Lambda the Ultimate

inactiveTopic Erlang is Icky
started 5/28/2003; 7:35:04 PM - last post 6/7/2003; 4:37:32 AM
Kimberley Burchett - Erlang is Icky  blueArrow
5/28/2003; 7:35:04 PM (reads: 776, responses: 13)
I just posted this article on my website, but I want to share it here so that everybody can tell me whether I'm off my rocker or not. Criticism more than welcome. I suppose I should post this to an Erlang list, but I don't follow any of them, so...

Before I start whining, let me make it clear that I have only just started using Erlang, and have written a total of 30 lines of code. This is just my initial impression.

Erlang is giving me a bad taste in my mouth for two reasons.

The first reason I don't like Erlang is that the syntax is reminiscent of Prolog, but the semantics are lacking everything that made Prolog interesting. Fine, there's no backtracking -- I can live without that. But what I miss most is that Erlang variables are not logic variables. What I mean by this is that you can't pass around unbound variables. The only time you can use an unbound variable is on the left-hand-side of an assignment, where it will be bound immediately thereafter.

This means that Erlang functions cannot be bidirectional, the way Prolog functions can. In Prolog, you can call a function foo(A, B) in such a way that either A or B can be the input variable, and the other can be the output variable. This feature lets you do all kinds of neat things: for example, using the same function for both creating and traversing a tree structure. [Aside: In the Oz language, unbound variables are used to provide an excellent mechanism for synchronizing with worker threads.]

The second reason I don't like Erlang is its anonymous function syntax. I have yet to find a way to write them that doesn't feel exceedingly clunky. For example, here is my first cut at a function to invert an ets table:

% This function takes an ets table SrcIdx, inverts it, and stores 
% the result in the ets table DestIdx.
invert_ets(SrcIdx, DestIdx) ->
        ets:foldl(fun({Key, Vals}, _) ->
                        lists:map(fun(Val) ->
                                        [{Val, Keys}]=ets:lookup(SrcIdx, Val),
                                        ets:insert(DestIdx, {Val, [Key|Keys]})
                                  end,
                                  Vals)
                  end,
                  unused, SrcIdx),
DestIdx.

This function is way too indented. I would have liked to move the inner fun up a level like this:

invert_ets(SrcIdx, DestIdx) ->
        InsertPair = fun(Key, Val) ->
                             [{Val, Keys}]=ets:lookup(SrcIdx, Val),
                             ets:insert(DestIdx, {Val, [Key|Keys]})
                     end,
        ets:foldl(fun({Key, Vals}, _) ->
                        lists:map(InsertPair(Key), Vals)
                  end,
                  unused, SrcIdx),
DestIdx.

... but unfortunately this version won't work, because it requires currying, which Erlang doesn't support. The only way for "Key" to be in scope is if the InsertPair fun is nested inside the other fun.

Erlang's implementation also loses some points for having exceedingly cryptic error messages. For example, this error message is trying to tell me that the function shell_default:lookup() is not defined:

10> lookup(Q, foo).
** exited: {undef,[{shell_default,lookup,[18,foo]},
                   {shell,local_func,4},
                   {erl_eval,exprs,4},
{shell,eval_loop,2}]} **

To be fair though, the error messages aren't really any worse than some of the type-related error messages from hugs (a haskell implementation), and I'm sure I would become well trained at decoding them in time.

One thing I really do like about Erlang is its use of atoms pretty much everywhere. For example, in the error message above, every single word in the error message is an atom, instead of a string. I like how lots of functions return 'ok' as their result, instead of a boolean value. I'm not sure why I like atoms so much -- it's probably just a style thing.

At this point in its development, it's very obvious that Erlang grew organically, in directions that were not envisioned from the beginning. Case in point: the fact that records require header files and a preprocessor.

Patrick Logan - Re: Erlang is Icky  blueArrow
5/28/2003; 9:27:05 PM (reads: 770, responses: 0)
it's very obvious that Erlang grew organically

Yes, the preprocessor implementation of records and other funky aspects struck me funny too.

I came to the conclusion that Erlang is not to be loved for its sequential functional programming as much as for its concurrent programming aspects.

Although I do like the pattern matching and single assignment. (Would prefer a better lambda syntax and a "let" syntax instead of single assignment. I didn't expect logic variables I think because I had been programming a bit in Haskell just prior to picking up Erlang around 1999.

I discontinued using Erlang for these reasons, being fond of the achievements in concurrency patterns more than the language per se.

Darius Bacon - Re: Erlang is Icky  blueArrow
5/29/2003; 12:34:01 AM (reads: 804, responses: 0)
The foldl/map example would probably be nicer with list comprehensions (which Erlang has). I agree that sequential Erlang isn't especially interesting.

andrew cooke - Re: Erlang is Icky  blueArrow
5/29/2003; 10:22:31 AM (reads: 760, responses: 0)
As someone who recently screwed up publicly (criticising Oz, as it happens), I'd say be careful when questioning languages you're still learning :o)

But I have to agree that logical variables are about as un-Icky as you can get. One related question - is there any practical use for difference lists with two "real" lists (ie where the second list is more than just an unbound variable)?

Also, anyone got time to dig up whether there's a Scheme or Lisp implementation of logic variables (I don't mean a Prolog-like emulation, but something that switches the basic language syntax - or is that a naive distinction)?

PS That looks like a stack trace, not an error message.

Patrick Logan - Re: Erlang is Icky  blueArrow
5/29/2003; 2:39:39 PM (reads: 732, responses: 1)
Andrew --- I'm not quite sure what you're looking for re: logic variables in Scheme/Lisp. You want logic variables integrated into the Lisp syntax and language?

Yes there are those. Google should turn up something, try Dan Friedman's page at Indiana.

You want Prolog syntax implemented in Scheme or Lisp? Maybe you can find one. The syntax is not too complex, so a translator to an existing Lisp syntax and implementation should not be too hard if you really want it.

Also there is a Python implementation that also implements a Prolog like syntax. I happen to be deep into Python at the moment so I have this link handy...

http://christophe.delord.free.fr/en/pylog/index.html

Kimberley Burchett - Re: Erlang is Icky  blueArrow
5/29/2003; 2:40:19 PM (reads: 739, responses: 1)
On the contrary, I wanted to make my questioning public, in the hope that someone would point out where I was mistaken. My intention was not to criticize so much as record and share initial impressions.

Unfortunately for Erlang, I had relatively high expectations for the language, and those were largely based on the mistaken belief that it was a logic language instead of a functional language. My current understanding is that Erlang excels at concurrent programming, not algorithmic programming. I may someday get around to exploring its concurrent features, but that's not the goal of my current language exploration project.

P.S. It's actually both a stack trace and an error message.

Patrick Logan - Re: Erlang is Icky  blueArrow
5/29/2003; 2:57:37 PM (reads: 710, responses: 0)
I agree with Kimberly --- if you learning, get your perceptions out there to be corrected if necessary. It's a good way to learn, and if someone runs with a misperception from a newbie as the gospel truth then, well, it wouldn't be the first time.

Look at what happened to Paul, for example. 8^)

Ehud Lamm - Re: Erlang is Icky  blueArrow
5/29/2003; 3:01:53 PM (reads: 766, responses: 0)
When I first looked at Erlang I too was expecting something more Prolog like. Anyway, I think the language is interesting in its own right.

One connection to Prolog which I was happy to discover is that the first proto-implementation of Erlang was based on a meta-circular Prolog interpreter.

andrew cooke - Re: Erlang is Icky  blueArrow
5/29/2003; 4:51:44 PM (reads: 744, responses: 0)
Thanks (I was looking for the former). I'll check out Dan Friedman's page asap.

Chris Double - Re: Erlang is Icky  blueArrow
5/29/2003; 6:35:14 PM (reads: 699, responses: 0)
Andrew, Screamer [1] for Common Lisp may have want you want as well:

http://lambda-the-ultimate.org/classic/messagx6200

It is currently being maintained as part of CLOCC:

http://clocc.sf.net

Chris.

Isaac Gouy - Re: Erlang is Icky  blueArrow
5/29/2003; 8:33:03 PM (reads: 702, responses: 0)
I suppose I should post this to an Erlang list, but I don't follow any of them, so...
So... this would be a good time to start? ;-)
It's a very friendly list.

Erlang *experts* might even have some insights for completing the Inverted Index task using Erlang.

After finishing a batch of Win32 Shootout programs in Ada (sorry Ehud!) it was obvious that I would need to learn much more before I could write "the Ada way". Fortunately, I did enough to encourage someone with Ada experience to take on the task.

Ada is Icky ?
Well that would have been just a tad presumptious ;-)

James Hague - Re: Erlang is Icky  blueArrow
6/6/2003; 10:34:42 AM (reads: 601, responses: 0)
I disagree with the prevailing sentiment that sequential Erlang is uninteresting. I think the combination of a pattern matching syntax, dynamic typing, and atoms makes it an interesting functional language. Yes, most functional languages have pattern matching, but in Erlang you can go a lot further with naively implementing functions as patterns, partially because the existing Erlang system takes some trouble to compile such constructs efficiently (for example, if you have a function that matches on 50 different atoms then it is turned into a hash table; the OCaml maintainers will tell you not to write code like this).

Sequential Erlang, from my point of view, provides the dynamism of Python and Lisp, but in a more modern functional setting. Interestingly, Erlang scores significantly higher than Python on most sequential benchmarks (by 2-4x in many cases). Erlang: The functional scripting language?

Luke Gorrie - Re: Erlang is Icky  blueArrow
6/7/2003; 4:17:30 AM (reads: 579, responses: 0)
I probably shouldn't be posting code, since I don't understand what the invert_ets function is actually doing or how it expects the input table to be laid out. However, here's a crack at a more curried-style version.

invert_ets2(S, D) ->
    foreach(fun({Key, Vals}) -> foreach(pair_inserter(S, D, Key), Vals) end,
	    ets:tab2list(S)).

pair_inserter(S, D, Key) -> fun(Val) -> [{Val, Keys}] = ets:lookup(S, Val), ets:insert(D, {Val, [Key|Keys]}) end.

(with foreach/2 imported from the lists module..)

Or maybe there is just a Better Way - what's the function's specification?

Luke Gorrie - Re: Erlang is Icky  blueArrow
6/7/2003; 4:37:32 AM (reads: 555, responses: 0)
Aha! Now I read the page on the website that says what the purpose of the program is. I think we can simplify the Erlang code by changing the data structure: instead of a set of {Key, [Value]} tuples with unique keys, we can use a set of {Key, Value} tuples with non-unique keys (i.e. a 'bag' ets table.) That way to reverse the mapping all you need to do is swap the elements of each tuple.

Then we can write the invert function in three lines. Here's a module with a simple test case.

-module(invert).
 
-export([test/0, invert/2, lookup/2]).
-import(lists, [foreach/2, sort/1]).
 
%% Insert the inverse mapping of table S into table D.
invert(S, D) ->
    foreach(fun({Key, Val}) -> ets:insert(D, {Val, Key}) end,
	    ets:tab2list(S)).
 
%% Lookup the mappings from Key in Tab.
%% Returns: [Value]
lookup(Tab, Key) ->
    [Val || {_, Val} <- ets:lookup(Tab, Key)].
 
test() ->
    ets:new(a, [named_table, public, bag]),
    ets:new(b, [named_table, public, bag]),
    ets:insert(a, {bar, foo}),
    ets:insert(a, {baz, foo}),
    invert(a, b),
    %% Assert that "bar -> foo; baz -> foo" inverts to "foo -> bar, baz"
    true = sort(lookup(b, foo)) == [bar, baz].