~technomancy/fennel

2 2

Termination conditions for loops

Details
Message ID
<875z20mf5k.fsf@whirlwind>
DKIM signature
missing
Download raw message
One common question we get in the Fennel channel on Freenode/Matrix is
how to break out of loops. There is no (break) form in Fennel for the
same reason lisps generally don't have early returns: code is easier to
understand when certain guarantees are upheld; for instance that the
return value of a function will always be found in a tail position, or
that reading carefully thru the body of a loop will not uncover some
information that changes the conditions of the loop.

So far the answer for breaking out of loops is either A) restructure it
to be a recursive form or B) in a pinch, fall back to the (lua :break)
form that allows you to embed any arbitrary Lua code in your Fennel
code. Neither feels fully satisfactory; I know not everyone shares my
love of recursion. =)

We've been talking a bit about how to introduce early termination from
loops without allowing arbitrary breaks in the loop body. Placing an
early termination condition inside the binding sequence would achieve
this goal.

For instance:

    (for [i 1 10
          (while (not= x 5))]
        (set x (+ x 1)))

Whatever we do should be supported across all four looping constructs:
for, each, collect, and icollect.

The big open questions at this point:

* Is `while` the right way to frame this? It certainly has the advantage
  that it parallels the existing `while` form and is very easy to
  understand what it does. We could do `until` instead though, which
  might map more closely with how it's likely to be used. Is it more
  common to look for a condition to become true in order to terminate,
  or false?

* Should we use parens around it like the above? In `for` we don't have
  this problem, but in the other iterator-based forms like `each` we
  have the issue that we don't know how many values the iterator will
  return, so we have to assume that the iterator is the last thing in
  the binding sequence. This no longer holds true with loop conditions.
  We could split it out a bit, but then you lose the benefit of the
  grouping from the parens. But this is arguably more consistent,
  because (while foo) makes it look like you are invoking the `while`
  macro inside the binding sequence! We want to minimize the number of
  places that parens mean something other than a function/special call.
  So maybe it's better to spread it out a bit:

    (each [k v (pairs tbl) :while (< (length out) 10)]
      (table.insert out (.. k v)))

  Putting a string in the binding sequence makes it clear that it is not
  a symbol that will be bound to a value from the iterator.

Let me know what you think!

-Phil
Details
Message ID
<CAAKhXoY4KZ81Wrf2CE+4i31b9QvG-4snkcO=jFeCKSHXkN_1QQ@mail.gmail.com>
In-Reply-To
<875z20mf5k.fsf@whirlwind> (view parent)
DKIM signature
pass
Download raw message
> * Is `while` the right way to frame this? It certainly has the advantage
>   that it parallels the existing `while` form and is very easy to
>   understand what it does. We could do `until` instead though, which
>   might map more closely with how it's likely to be used. Is it more
>   common to look for a condition to become true in order to terminate,
>   or false?

I think `:until` can resemble the intentions very clearly. We loop
/until/ certain condition is met.

As for notation, I feel that keyword-string is the best way to express
this. As you've already mentioned on IRC, another alternative would be
`&while`, and while this syntax is convenient in terms that it is
forbidden to use anywhere, it is also used for binding forms, which
makes it out of place for conditions. Same thing with parenthesis.

Another solution would to include hash-table?

    (each [i v (ipairs tbl) {:until (> x 10)}] body)

But this is bad too, because it looks like the table will hold the
result of comparison. So I think keyword-string is good enough here,
and the cleanest form.

-- 
Andrey Listopadov
Details
Message ID
<874khhm04p.fsf@whirlwind>
In-Reply-To
<CAAKhXoY4KZ81Wrf2CE+4i31b9QvG-4snkcO=jFeCKSHXkN_1QQ@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
Andrey Listopadov <andreyorst@gmail.com> writes:

> I think `:until` can resemble the intentions very clearly. We loop
> /until/ certain condition is met.

Yeah, I think this is true; it feels like a better fit than while.

I've implemented it in the patch I sent earlier.

thanks
Phil
Reply to thread Export thread (mbox)