[erlang-questions] Funargs: Ruby-like blocks for Erlang

Richard O'Keefe ok@REDACTED
Fri Jul 22 00:48:58 CEST 2011


Ruby blocks are a crippled perversion of Smalltalk blocks.
And Smalltalk blocks are practically the same as the way
functions were written in Wirth's language Euler.

>     [1,2,3,4,5].map do |n|
>         n * 2
>     end

in Smalltalk this is just

	(1 to: 5) collect: [:n | n * 2]

The Smalltalk collection classes provide *oodles* of opportunities
to use them, and no few of the methods require *two* blocks.
For example,

	aCollection
	  do: [:each | Transcript print: each]
	  separatedBy: [Transcript space]

which doesn't really fit well into Ruby syntax.
Another example would have been try-catch, which _could_ have been
a function:

try(Set_Up,              % () -> Info
    Tear_Down,		 % (Info) -> ()
    Body,		 % (Info) -> Answer
    Handler_List)	 % [(Info, Exception) -> Answer]

> However, I'd rather ask: can Erlang have something like Ruby like
> blocks? Yes, yes it can.

Well yes, it does.  They are called funs.
> 
> The linked patch implements a feature I'm tentatively calling
> "funargs" (yes, I know, this name has prior usage in the funarg
> problem. If you don't like it suggest a better one!)

Almost ANYTHING, up to and including "tinklebats", would be
better than twisting existing words into incompatible meanings.
Since the Latin word "fungor" means "to occupy oneself with
anything", "perform", "execute" (Cassell's Latin Dictionary),
why not call them "fungors"?

> The patch adds a new 'do' keyword and otherwise copies the Ruby syntax
> outright.

> Let's compare what Erlang lets you do now with what the
> syntax this patch provides allows.
> 
> Before:
> 
>    mnesia:transaction(fun() ->
>         mnesia:write(#thing{foo=1, bar=2, baz=3})
>    end).
> 
> After:
> 
>    mnesia:transaction do
>         mnesia:write(#thing{foo=1, bar=2, baz=3})
>    end.
> 
> That's marginally better.

It's a very small margin, and possibly a negative one.
Should it not be

	<function>([<exprs>]) do [<pats> ->] <body> end
=>
	<function>(fun ()     -> <body> end[, <exprs]>)
or	<function>(fun <pats> -> <body> end[, <exprs]>)
?
So should it not be
	mnesia:transaction() do
	    mnesia:write(#thing{foo=1, bar=2, baz=3})
	end
for consistency?

> How about some more examples?
> 
> Before:
> 
>    lists:map(fun(N) ->
>        N * 2
>    end, [1,2,3,4,5]).
> 
> After:
> 
>    lists:map([1,2,3,4,5]) do |N|
>        N * 2
>    end.
> 
> Again, it's marginally better.

This is where the margin turns negative.
What has mapping to do with "DO"?

And YEEK!  The argument list here
looks like nothing else in Erlang whatever!
Surely surely surely Erlang should look like Erlang!

    lists:map([1,2,3,4,5]) do (N) -> N*2 end

(As a Smalltalk programmer, I really wish that Ruby
hadn't taken Smalltalk's syntax for *local variables*
and twisted it to mean block parameters.)

The vertical bar notation here is NOT a good fit for
a language where function arguments involve pattern
matching, and patterns can involve vertical bars.

> What if there are more arguments?
> 
> Before:
> 
>    lists:foldl(fun(Octec, Acc) ->
>        Acc bsl 8 bor Octet
>    end, 0, [127,0,0,1]).
> 
> After:
> 
>    lists:foldl(0, [127,0,0,1]) do |Octet, Acc|
>        Acc bsl 8 bor Octet
>    end.
> 
> In this case I definitely prefer the latter form. Try to imagine
> functions with much more expressions in the fun body.

Try to imagine function calls with many more funs in their arguments!

By the way, it's now a couple of years since I proposed syntax like

	(Acc where Acc = 0 then Acl bsl 8 bor Octet
	       for Octet <- [127,0,0,1])

the three patterns being

	'(' <expr> <opt-where> opt-generators-and-guards ')'
	'[' <expr> <opt-where> opt-generators-and-guards ']'
	'{' <expr> where <do-bindings> opt-generators-and-guards '}'

opt-where = 'where' <do-binding> {',' <do-binding>}... | <empty>
<do-binding> = <pattern> '=' <expr> ['then' <expr>]
opt-generators-and-guards = {'for' | '||'} {generator|guard}
	{',' {generator|guard}}... | <empty>

This has roots in ISWIM, Scheme, Clean, and Erlang.

> So I'll admit so far none of these examples are *particularly* more
> compelling than their equivalent "fun" forms. Where blocks get really
> interesting is when you nest them, particularly when building what
> Rubyists refer to as DSLs.

They did not invent the idea.  I think Erick Sandewall may have been
the first to write about embedded DSLs (in Lisp) back in the 70s.
There have been two USENIX conferences about DSLs.

> Below is a hypothetical example I think
> could actually be implemented using this syntax, perhaps with a custom
> error handler, which I admit would be a complete hack, but I think
> it'd be pretty interesting. I tried to translate an example builder
> template from Ruby available here:
> 
> http://danengle.us/2009/05/generating-custom-xml-for-your-rails-app/
> 
> Let's imagine we have a custom error handler and a parameterized
> module "Xml" which is thunking to a process that's building an XML
> document for us on the fly. We could build XML in Erlang as follows:
> 
>    Xml:posts do
>        lists:each(Posts) do |Post|
>            Xml:post do
>                Xml:title(Post#post.title),
>                Xml:body(Post#post.body),
>                Xml:published_at(Post#post.published_at),
>                Xml:comments do
>                    lists:each(Post#post.comments) do |Comment|
>                        Xml.comment do
>                            Xml:body(Comment#comment.body)
>                        end
>                    end
>                end
>            end
>        end
>    end
> 
> And voila, you have a fairly decent DSL for building XML from Erlang.

I have serious trouble reading this,
and part of the reason is that I see <thing> do <thunk>
and cannot help but read that as "do <thunk> for every element of <thing>".
This is reinforced by the fact that in
Erlang *everything* that is a function call has a pair of parentheses;
"Xml:posts" looks far more like a variable than like a function call.

The thing is, we already _have_ a way of writing XML in Erlang:

	{posts, [
	   {post, [
	      {title,        [Post#post.title]},
	      {body,         [Post#post.body]},
	      {published_at, [Post#post.published_at]},
	      {comments, [
	         {comment,   [Comment#comment.body]}
	      || Comment <- Post#post.comments]}}
	|| Post <- Posts]}

which is 9 lines to your 16 with no language extensions whatever.

Give me a data structure, bursting with the seeds of its own
transformation, rather than a tangle of commands.

Ruby is an imperative language working by side effects.
What works well for Ruby doesn't work so well for Erlang.



More information about the erlang-questions mailing list