[Openmcl-devel] LOOP parallel for/as termination eval order issue.

Kaz Kylheku kaz at kylheku.com
Mon Oct 18 14:53:39 PDT 2010

On Mon, 18 Oct 2010 05:56:47 -0600 (MDT), Gary Byers <gb at clozure.com>
> If you think that X can be initialized to some predictable value before
> the termination test, what on earth would that value be ?

I believe that the purpose of the text ``meaningless value
of the correct type'' in must be to support type
declarations. Since we aren't declaring X to have some
type (no [type spec] is present in the clause) its value must
be NIL.

If the following doesn't return NIL, I believe there
is a problem.

  (loop for x in () finally (return x))

Do some Lisps return something other than NIL?

I can get CCL to return non-NIL like this:

  (loop for x of-type float in ()
        finally (return x))
  -> 0.0

CLISP, same thing.

Another remark is that In Common Lisp it is
idiomatic to inquire about the first element of an empty list
with (first nil) without caring that it's empty.
In the absence of a type-spec, X could simply be initialized
using (car <list>) without caring that list may be empty.

> Even CLISP believes that the termination test has to precede assignment to
> X.

My reaction is that "termination" can refer to "termination of the
as well as "termination of the loop". Just because we have to test
list termination doesn't imply that we must also
terminate the loop right there.

> The issue is that CLISP sometimes decides that the assignment to Y can be
> moved to a point before the assignment to X and before the termination test
> and that other times it can't.

I'm now regarding that inconsistency should be regarded as a bug.

Be the spec as it may, the reality is that Lisp implementations
LOOP in a certain way (perhaps traditional or whatever), and so that
is what we have to follow in the trenches.

Wishing that it was different isn't going to magically cause a
dozen implementations (or whatever) to fix themselves.

My program probably worked by dumb luck by relying on buggy
CLISP behavior which superficially looked like it was
implementing the termination-test deferring behavior
that is allowed by the spec, when in fact what may be going
on is whimsical.

Though everything works, I still want to make sure that what I'm
doing in my program is (de-facto) portable.

After this debate, I think that the portability recommendations
can be tentatively summarized like this:

- a loop iteration control clause can trigger loop termination
  during variable initializations or stepping, causing subsequent
  clauses not to be processed, leaving their implied
  initializations or steppings not done. The implied effects
  of prior forms are done, however. So for instance if
  there are two clauses:

    for x in form1 then form2
    for y in list

  and it happens that list runs out of elements, then
  "for y in list" may trigger instant loop termination
  when it detects that the loop has no more elements.
  However, the effect is already settled that X has
  received the value of FORM1 (if the first iteration of
  the body has not yet occured) or FORM2 (if this is just
  prior to what would have been the second or
  subsequent iteration).

- thus, variable assignment and steppings made prior to the
  terminating-triggering clause are stable, and their
  values and other side effects can be used in the epilogue
  (finally clauses)

- variable steppings or assignments following the clause which
  triggered termination may or may not made.
  Some implementations (notably CLISP) sometimes
  reorder the clauses, such that the termination is done later.
  Deferring termination tests does not violate the letter
  of the spec whose wording strongly suggests that in fact
  termination tests can be deferred. (Although doing this
  inconsistently can be regarded as buggy behavior nevertheless).
  Thus, extending the prior example, if we have:

    for x in form1 then form2
    for y in list
    for z = (pop other-list)

  if "for y" triggers termination, the (pop other-list)
  call and update of z may or may not take place.
  So not only may you not rely on the value of Z
  in the loop epilogue, but also not on the state
  of the variable OTHER-LIST.
  (So for portability, don't do stuff like this!)

- When multiple clauses are connected by AND and one of 
  them triggers termination, it is probably best not
  to rely on any of variable updates implied by the
  others to have occured (or not to have occured). The
  safest assumption is that all bets are off.
  Avoid AND when surrounding variables are
  being accessed in a FINALLY.

More information about the Openmcl-devel mailing list