I expect the 4-argument version of makelist
to evaluate its 3rd and 4th arguments exactly once each, its 1st argument once per iteration, and its 2nd argument not at all. This is the expected behavior:
makelist(print(a,i),print(b,i),print(c,2),print(d,4));
c 2
d 4
a 2
a 3
a 4
=> [2, 3, 4]
But that is not what it does. In fact, makelist
evaluates its second argument on each iteration:
makelist(print(a,i),print(b,i),print(c,2),print(d,4));
c 2
d 4
b i
a 2
b i
a 3
b i
a 4
=> [2, 3, 4] << This part is OK
Why?
You might think this is perversely useful if you want the iteration variable to be able to be variable (really bad idea, the iteration variable should be local), but that doesn't work:
var: 'i$ makelist([i,var],var,2,4);
=> [[i, 2], [i, 3], [i, 4]]
It's true that makelist
needs to evaluate its second argument in the 2-argument case:
makelist(print(a,i),print(b,3))
b 3
a i
a i
a i
=> [i, i, i]
But it should only do that if it has exactly two arguments.
Then we have this weird case:
makelist(print(a,i),print(b,3),5,6);
b 3
a i
b 3
a i
=> [i, i, i]
where it sees to be OK with a numerical second argument, but is using the third and fourth for the count, and this weird case (which you'd think would work like the previous one):
makelist(print(a,i),3,2,3);
Only symbols can be bound; found: 3
What a mess!
I'm generally in favor of fixing up stuff like that, in the interest of making it easier to predict what's going to happen.
makelist
being pretty commonly used, it seems like it merits special attention -- if you have any bug fixes, I would be interested to try it.I must say I actually like the way makelist behaves. I know, I know, "perversely useful" though Maxima certainly has features far more quirky than this one. Here's a variation that I was just playing with:
What this example tells me even without looking at the implementation of makelist is that it is not actually using the second argument as an iteration variable; rather, it simply assigns the (internally maintained) iteration variable to its second argument in each iteration.
Is this a Bad Thing? Does it need fixing? The behavior actually makes sense to me. But my biggest worry would be that by fixing this, we might break existing code that relies (maybe it shouldn't but who am I to judge?) on side effects of this behavior.
Here's another bit reported by @willisbl a few days ago.
"To find the number of terms,
makelist
callsfloat
on the difference of the fourth and third arguments. The functionfloat
looks upnumerval
data. SoThis is OK, I guess, but is it predictable from the user documentation? No, I don't think so."
@vttoth I'm inclined to look at whether a concise description of the behavior is possible; if so, then fix up the documentation, otherwise formulate a concise description and make the code match it.
I would be interested to consider requiring that the second argument be a mapatom, and to assign it but not evaluate it. I'm not yet ready to claim that must be the case, only that I'm willing to consider it.
If we change the behavior, we should ideally make some previous behavior an error, as opposed to just getting a different result.
Maybe through an example?
Then there's also this:
Again, this seems quirky but not any more quirky than many other Maxima features. Not a bug that needs fixing I think, and fixing it may break thing elsewhere. Documenting it may indeed be the right thing to do.
I agree with Dodier. The current behavior really is perverse, is not part of the documented behavior of makelist, and has no obvious benefit.
In fact, the second argument should be required not just to be a mapatom, but an assignable atom/symbol. This would make it consistent with other cases like
sum
andproduct
. Allowing it to be (for example) a subscripted variable allows side-effects in the subscript calculation, which isn't necessary when you're talking about a formal variable.I suggest that we consider removing the calls to
float
frommakelist
.I did this experiment and the testsuite, including share, gave no unexpected failures.
But
sum
also consultsnumerval
facts, at least for the limits of the summation. So for consistency, we should retain the calls tofloat
.float
and$bfloat
are called byis
, so thatdo
can support things like this:Isn't that kind of confusing? How does
is
now whether to usefloat
forbfloat
? This would be important for something likefor i:%e thru %e-<tiny rat> do print(i)
, where<tiny rat>
is some tiny rational such thatfloat(%e-<tiny rat>)
would be the same asfloat(%e)
, but ifbfloat
were used, a value just smaller thanbfloat(%e)
would have been returned?Actually,
for i:%e thru %e+<tiny rat> step <tinier rat>
works just fine sinceis
doesn't need to use(b)float
in that case -- it can do exact comparisons.It's certainly possible to find bad cases of various kinds, although I'm pretty sure that it's extremely rare for users to want to do things like
but in some sense it's a side-effect of handling rats and bfloats -- Maxima doesn't have imperative arithmetic primitives; it just uses simplification and
is
.So this isn't really a
do
issue but anis
issue.And even with pure floats, you can get problems, e.g.,
for i step 1e-30 ...
will always be an infinite loop since 1+1e-30 = 1.0.Last edit: Stavros Macrakis 2024-07-18