forget

C.Reinke <>
Fri Jun 7 20:37:37 CEST 2002


> Please correct me if I am just confused. Language extensions are grat :-) 

You mean: extensible languages are great!-)

Languages should be general enough to let you do what you need,
conveniently, without having to ask someone else for extensions.
That could mean convenient meta-programming facilities, as in Lisps,
or it could mean convenient higher-order programming facilities, as
in more recent functional languages. Both allow you to extend your
general-purpose language by domain-specific languages, in which it
becomes easier to tackle problems in your domain.

If programmers can't do this, extensions have to be added centrally.
And after patching too many extensions into a language spec, you'll
find yourself with an unmanageable language, lacking any remainders
of clear design, but offering a bewildering variety of features that
never quite cooperate in the way you'd like them to. But that's an
old problem (Dijkstra wrote about it in his "Humble Programmer";-).

> but in this particular case, would they solve more than a small
> part of the problem? It was pointed out earlier that in practice,
> the successive calls tend to look something like:
> 
> {ok, A} = foo(Q),
> {B, C} = bar(A),
> {value, [D]} = baz(B, C),

This still has some regularity, which one could try to capture:

  uncurry(F,{X})     -> F(X).
  uncurry(F,{X,Y})   -> F(X,Y).
  uncurry(F,{X,Y,Z}) -> F(X,Y,Z).

  do_with (Values, [])    -> Values;
  do_with (Values, [H|T]) -> V = (uncurry(H))(Values), do_with(V, T).

Then your calls could become:

  do_with({Q},[
    fun foo/1,
    fun (ok,A) -> bar(A) end,
    fun baz/2
  ]),

Note: with Erlang's syntax, this isn't much shorter, but it still
reduces the mental clutter - you don't have to think about whether
the code conforms to the pattern, it's obvious from the call to the
higher-order function encapsulating the pattern (giving the benefits
familiar from behaviours, for user-defined patterns).

It's the irregularity of Ulf's example that reduces the gains from
such an approach. Variable-arity functions are straightforward in
Erlang (no static type system trying to get in the way), but
back-references to results returned earlier in the chain would
already require something more like monads, and then Erlang's prefix
calls, parentheses and end's would start to pile up somewhere. But
if there's no or little regularity in the chain of calls, there's
little one could abstract into a higher-order control construct.

Of course, one might ask whether the code should be that irregular
in the first place (but who am I to say?-), as that means there is
no other way to read and understand the code than looking at all the
details and carefully tracking the changes in usage from call to call.
It's not just the variable names, it's that each part of the code
does its own thing, and has to be understood on its own and in all
its connections to the usage context.

> >Why not permit unbound variables in function calls, ..  I looked
> >at logic vs functional languages a while ago, and most of the
> >examples where logic languages won where down to this late, but
> >still single-assignment use of variables. 
> 
> Could you please explain in short why would this be a good idea?
> It is a long time since I used Prolog... Would these variables
> function as "out" parameters? Isn't it clearer to read and write
> code that returns all values as the result, not intermix them with
> the arguments? I know Prolog uses that for neat stuff, but it is a
> different world (or so I see it).

It's been a long time for me as well, but the worlds are not so
different in practice (I used to read the acronym as PROgraming xor
LOGic:). Passing uninstantiated variables in Prolog is similar to
"out" variables in other languages (cleaner, though, as there can be
only a single value in each branch, and variables are still simply
placeholders for values, as in Erlang, not storage locations, as in
other languages).  

There is no need for the callee to instantiate them, either - they
can be put whereever you need them, as placeholders for the single
value which you give them when it becomes available to you. So it is
not so much about mixing results with arguments (which is necessary
in Prolog as predicates only return success, at best) but about
being able to delay the instantation to a more convenient time -
it's still single-assignment, as in Erlang, but you gain
flexibility.  There's no need for non-determinism, no conflict with
fp ideas, no different world (as Joe and Klacke have pointed out,
you might want to rule out potentially uninstantiated variables in
message passing constructs to avoid runtime checks).

Some of the typical examples are traversals of data structures
(lists/trees/..) containing definitions and uses of items, where you
may encounter places of usage before places of definition. With
logical variables, or with recursion and lazy evaluation, you can
handle these with a single traversal, otherwise you need two (one to
extract the definitions, the second to replace the uses).

Very few functional languages have experimented with the "neat
stuff" one can do with logical variables independent of other logic
programming concepts, but as Erlang grew out of Prolog, I was
interested to learn why logical variables were dropped almost
completely (the odd flat scoping, and the resultant need for
forget(), are perhaps the only reminders). The focus on concurrency
and simplicity was the answer to that question.

> >     - non-nesting variable scope (fortunately not adopted for
> >       the higher-order extensions)
> >       so one cannot reuse the same variable name, as one usually
> >       can with non-recursive lets.
> 
> I am not sure if this would be very useful, since there is no construct 
> similar to "let". How would the nested scope be defined? 

I guess I'd prefer something like "..,let <lhs>=<rhs>,.." to stand
for an implicit "..,forget(<vars(lhs)>),<lhs>=<rhs>,.." instead of
having an explicit forget bif or the current f() in the shell.

> >     - relatively heavy-weight syntax
> >       making the use of higher-order functions less convenient
> >       than in Haskell (MLs have the same problem). So instead
> >       of defining their own higher-order control constructs
> >       (which otherwise tends to be home ground for functional
> >       languages), people are asking for language extensions
> >       (the same happened with behaviours).
> 
> I am not very familiar with Haskell, but aren't it's capabilities for 
> composing high-order funs (mostly) a result of currying and the syntax 
> without parentheses? 

Currying, no needless parentheses, user-definable infix operators
(function composition is just an infix dot, so that doesn't impede
readability). That's the nice/simple part of Haskell - no
spectacular features responsible for the advantage, just a carefully
considered mix of small things that tend to work together well and
ensure that the language syntax stays "out of the way".

> This might be introduced in Erlang, but would it still be Erlang
> afterwards? (*deep thought ;-)

Why not? Erlang shines in other areas, it's "Erlang-ness" doesn't
come from these things, does it?-) And if such general improvements
could remove the need for ad-hoc extensions that make the language
more complex, wouldn't that be preferable?

Claus



More information about the erlang-questions mailing list