[m-dev.] Adding Loops to Mercury

Matt Giuca matt.giuca at gmail.com
Fri Jan 3 11:14:29 AEDT 2014


The problem with suggesting developers use existing abstractions or make
their own is that it can (in some circumstances) result in rather verbose,
hard to read code. foldl is particularly hostile to state variables (which
I use a lot).

As I see it, there are two "inconveniences" with using foldl in place of
dedicated for loop syntax:

   1. The need to explicitly define a predicate and the rather awkward
   syntax of doing it in-line, as well as having to a) explicitly list both
   the input and output of each state variable, and b) explicitly list the
   modes of all parameters and the predicate itself.
   2. The fact that you must place the input list and variables to be
   updated at the end of the "loop body", making it hard to read what is going
   on until you reach the end.

To illustrate these points, consider a function that takes an assoc_list of
name-value pairs and prints them out neatly:

:- pred print_key_values(assoc_list(string, string)::in, io::di, io::uo) is
det.
print_key_values(List, !IO) :-
    list.foldl((pred(X-Y::in, !.IO::di, !:IO::uo) is det :-
        io.write_string(X, !IO),
        io.write_string(" = ", !IO),
        io.write_string(Y, !IO),
        io.nl(!IO)
    ), List, !IO).

There is no need to define a "print_key_value_pair" predicate since I only
need it in this one spot, so I use a lambda predicate. To Point 1, note how
awkward it is to place the predicate inline, requiring extra parens around
the predicate. I need to explicitly mention !.IO and !:IO (in addition to
mentioning that I am passing !IO at the end), and I need to explicitly list
the modes of the parameters and the determinism of the predicate. Being
explicit about all this is fine when writing a separate predicate, but here
I am really just trying to iterate over a list and do something with each
element. I should not have to care about modes or determinism at this
point. To Point 2, note that you can't see which list I am iterating over,
nor which state variable I am updating, until the very end. There is also
no strong visual association that says "X-Y is an element of List" (unlike,
say Python, where you would just write "for (X, Y) in List").

I would propose a for loop syntax for Mercury which can be used something
like this:

:- pred print_key_values(assoc_list(string, string)::in, io::di, io::uo) is
det.
print_key_values(List, !IO) :-
    for X-Y in List [!IO] (
        io.write_string(X, !IO),
        io.write_string(" = ", !IO),
        io.write_string(Y, !IO),
        io.nl(!IO)
    ).

The features of this syntax are:

   1. Clearly shows that X-Y is the elements of List, and that List is the
   list being iterated over.
   2. Only need to specify the accumulators once (!IO), instead of
   specifying them both in the predicate head and as separate arguments.
   3. Works naturally with the !S state variable notation, not requiring
   the user to break the illusion of S being a single variable and specify !.S
   and !:S.
   4. Does not allow the "invention" of new accumulator names; instead, you
   are merely specifying existing variables from the outer scope (typically
   state variables) that may be updated in the body of the loop.
   5. Does not require the user to have to decide whether to use foldl,
   foldl2, foldl3, etc. It works for any number of accumulator variables.
   6. It keeps the idea that the user must explicitly specify which
   variables can be updated by the loop body. You can't just go updating any
   state variable in the outer scope, for example.

This is intended to be a syntactic sugar around list.foldl:

for *element* in *iterable* [*accumulators*] (*body*)

Where *element* is a term of type T, *iterable* is a term of type list(T),
*accumulators* is a list of variable names of even length (where a state
variable counts as both the !. and !: variable), and *body* is a goal.

This expands to:

list.foldl*n*((pred(*element*::in, *accumulator1*::*accumulator1_mode*,
..., *accumulatorn*::*accumulatorn_mode*) is det :- *body*),
    *iterable*, *accumulator1*, ..., *accumulatorn*).

Note that the expansion needs to work out the correctly numbered version of
foldl, as well as the mode of each accumulator variable.

It doesn't explicitly require state variables; you could just pass two or
more normal variables as accumulators, but it would usually be used with
state variables. I'm sure this could be generalized better (for example,
allowing it to expand to use any of the many permutations of map, fold,
filter, etc, as Peter hinted at). Another nice generalization would be if
it worked on any iterable object, not just lists. For example, if it could
work out that if *iterable* is a map, it should call *map.foldl*. That
could be done by introducing a type class *iterable* which provides a foldl
method, or even a full-blown iterator implementation, and then having the
for syntax expand to that. But this is just a quick proposal, not intended
to be taken too seriously.

Now having said all of this, I agree with some of the sentiments expressed
above that such a feature does not "feel" like it belongs in Mercury. I
wouldn't be surprised if this was deemed "un-Mercurial" for being too
imperative. But I'm just saying that I would have found it useful all over
the place (which perhaps speaks more about the way I think when I write
code -- imperatively). And it does kind of fit with the existing state
variable notation, as well as some of the existing quantifying goals like
'some'.


On Thu, Jan 2, 2014 at 10:09 PM, Paul Bone <paul at bone.id.au> wrote:

> On Thu, Jan 02, 2014 at 07:30:18PM +1100, Julien Fischer wrote:
> > On Thu, Jan 2, 2014 at 7:26 PM, Paul Bone <paul at bone.id.au> wrote:
> >
> > > On Thu, Jan 02, 2014 at 06:20:27PM +1100, Julien Fischer wrote:
> > > > On Thu, Jan 2, 2014 at 5:50 PM, Michael Day <mikeday at yeslogic.com>
> > > wrote:
> > > >
> > > > > Hi Paul,
> > > > >
> > > > >
> > > > >  foreach I=std::fromTo(1,1000) do
> > > > >>>     %...whatever
> > > > >>> end foreach.
> > > > >>>
> > > > >>
> > > > >> I think that such a feature would be considered "not in the
> spirit of
> > > > >> Mercury" by most developers.
> > > > >>
> > > > >
> > > > > Light syntax and efficient compilation for lambdas is enough:
> > > > >
> > > > >     foreach(1 `..` 100, func(X) = blah)
> > > >
> > > >
> > > > Which is pretty much already supported, e.g.
> > > >
> > > >       map(func(X) = X * 2, 1 `..` 100)
> > > >
> > > > (Use a quantifier like foreach seems like the wrong name to use
> > > > for some that is transform the list of integers using a function.)
> > >
> > > foreach would be a predicate that is very similar to list.foldl.
> > >
> >
> > It wouldn't, you would need to provide the initial value of the
> accumulator
> > somewhere.  In any case, my point was that foreach is not a good name
> > for such a thing.
>
> We do that in list.foldl as a parameter.  I don't see why it should be any
> different.  Anyway, I'm not saying that Mercury should add foreach
> anywhere.
> I'm saying that anyone who wants to create loops can write a wrapper around
> foldl or anything else to create the abstractions they want.  Or just use
> foldl because it is already such an abstraction.
>
>
> --
> Paul Bone
> http://www.bone.id.au
> _______________________________________________
> developers mailing list
> developers at lists.mercurylang.org
> http://lists.mercurylang.org/listinfo/developers
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/developers/attachments/20140103/fc686016/attachment.html>


More information about the developers mailing list