[erlang-questions] Matthew Sackman's "cut" proposal

Richard O'Keefe ok@REDACTED
Mon Jul 11 05:00:26 CEST 2011

Matthew Sackman has a presentation
on extending Erlang syntax to make it a nicer functional language.

I'd like to briefly discuss his "cut" proposal.

First, some background.

Pop-2 had the idea of "frozen formals".  You could provide a
function with some of its arguments, like this:

	f(% foo, bar %)

What you got was a closure that pushed the "frozen formals" onto
the stack after any other parameters and then jumped to the original
function.  It was not quite the same as

	fun (X, Y) -> f(X, Y, Foo, Bar) end

because the frozen formals were evaluated at the time the closure was
created, not the time it was called.

In functional languages where functions notionally have one argument,
you get the same effect by passing the leading arguments but not all
of them.  Since
	f X Y Z = E
is equivalent to
	f = fun (X) -> fun (Y) -> fun (Z) -> E end end end
in those languages, passing just a few of the arguments is what
notionally happens anyway in a "saturated" function call, so no extra
syntax is needed.

Erlang is _not_ "Curried" like SML or Haskell, not even notionally.

Possibly the nearest analogue for Erlang would be Eiffel (ECMA-367),
	agent <method call>
signifies a closure, with '?' indicating an "open" (unfilled)
argument, and other arguments indicating "closed" (filled) ones,
evaluated when the 'agent' expression is evaluated, not when it is called.

Without the aid of extra combinators, Pop-2 can only pre-supply trailing
arguments, Haskell can only pre-supply leading arguments, and Eiffel can
pre-supply any mix of arguments.

Matthew Sackman's proposal is very close to Eiffel agent expressions.
He uses '_' instead of '_' and 'cut(...)' instead of 'agent ...', but
it's basically the same idea.

I may say here that the word 'cut' makes absolutely no sense to me.

Let's look at his first three examples:

% Current Erlang
info_all(VHostPath, Items) ->
    map(VHostPath, fun (Q) -> info(Q, Items) end).

% With "cut"
info_all(VHostPath, Items) ->
    map(VHostPath, info(_, Items)).

% or
info_all(VHostPath, Items) ->
    map(VHostPath, cut(info(_, Items))).

% Current Erlang again:
info_all(V_Host_Path, Items) ->
    [info(Q, Items) || Q <- V_Host_Path].

% Current Erlang
backing_queue_timeout(State = #q{backing_queue = BQ}) ->
        fun (M, BQS) -> M:timeout(BQS) end, State).

% With "cut"
backing_queue_timeout(State = #q{backing_queue = BQ}) ->
    run_backing_queue(BQ, _:timeout(_), State).

% or
backing_queue_timeout(State = #q{backing_queue = BQ}) ->
    run_backing_queue(BQ, cut(_:timeout(_)), State).

And here's just what I learned to hate in Eiffel:  the
correspondence between place-holders and parameters
is positional.  Suppose I want

	fun (BQS, M) -> M:timeout(BSQ) end

then I am out of luck.  You _can't_ do that with "_".

% Current Erlang
reset_msg_expiry_fun(TTL) ->
    fun (MsgProps) ->
        MsgProps#message_properties{expiry = calculate_msg_expiry(TTL)}

% With "cut"
reset_msg_expiry_fun(TTL) ->
    _#message_properties{expiry = calculate_msg_expiry(TTL)}.

% or
reset_msg_expiry_fun(TTL) ->
    cut(_#message_properties{expiry = calculate_msg_expiry(TTL)}).

A reminder here that there is a difference between a 'fun' equivalent
in other languages and a 'cut' equivalent:  in the 'fun' equivalent
the other arguments are evaluated when the closure is *called*, while
in the 'cut' equivalent the other arguments are evaluated when the
closure is *defined*.  If calculate_msg_expiry/1 has side effects,
the two functions of reset_msg_expiry_fun/1 are not (if "cut" is
implemented for least surprise) equivalent.

Now let's look at some other funs:

    AC = spawn_link(fun() -> init(Init, KernelApp) end),

Without the "cut" keyword, "_" is no help because you would
not be able to tell
	init(Init, KernelApp)	-- the call
from	init(Init, KernelApp)	-- the closure
So it's clear that the "cut" keyword (or rather, something meaningful
to play the same rôle) is necessary if we want to do

    AC = spawn_link(cut(init(Init, KernelApp))).

               fun([{{loaded, AppName}, #appl{descr = Descr, vsn = Vsn}}]) ->
                       {true, {AppName, Descr, Vsn}};
                  (_) ->

"_" cannot help here because
 - there are two clauses
 - one of them involves pattern matching.

    map(fun([Key, Val]) -> {Key, Val} end, <expr>)

"_" cannot help here because of pattern matching.

            lists:foreach(fun(Appl) ->
                                  ets:insert(ac_tab, {{loaded, Appl#appl.name},
                          end, NewAppls),

This could be done better as
	[ets:insert(ac_tab, {{loaded, Appl#appl.name}, Appl}) || Appl <- NewAppls]

	fun ({Name, Id}) ->
	    case Id
	      of ...

"_" cannot help here because the body is not a simple function call.
The argument involves pattern matching.
If it were fun (Name, Id) -> case Id of ...
we'd still be stuck because Name isn't the first parameter to appear in the body.

I began a survey of 'fun's in the kernel/ directory.
Out of the first 22, one could use "_".  I have to go and pick up my daughter
from school, so I don't have time to finish this survey yet.

However, it doesn't look like that much of a win so far.

More information about the erlang-questions mailing list