Constructive criticism of Erlang

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Chris Pressey
Hi again.

I hope that everyone can understand: it is because I love Erlang that I
must critique it.

Keep in mind that it is not only Erlang that is guilty of the flaws I
list below - many other high-level languages are as well.  (If I were to
construct a list like this for Perl or C++ it could very well fill
several volumes :-)

Also, please excuse me if these issues have already been addressed in
the newest versions of the language/revisions of the compiler.

I realize I might not have much luck in catching the ear of anyone who
is actually responsible for future design of the Erlang language, on
this list - presumably, Ericsson is the responsible party for that,
open-source does not mean open-design.  However, I still think these are
valid thoughts which might be interesting to Erlang programmers.

1. Having semicolons as statement seperators instead of statement
terminators is basically *annoying* during program maintainance (while
adding or removing a statement.)  Having an extraneous semicolon
(especially before an 'end' token) generate a warning rather than an
error would be more pleasant.

2. I don't think I agree with the idea that only some "specially
selected" functions should be allowed in guards.  This makes guards
semantically unorthogonal.  I think guards should be thought of as mere
syntactic sugar, and the compiler will choose what it determines to be
the most efficient manner of implementing the guard.  This would help in
writing more readable programs.

I understand this makes the compiler more complex though, but even under
the current system, more BIFs should be allowed in guards.  I don't see
why I can't say "when element(N, T) == 0", for example; that's fairly
lightweight, no?

3. Speaking of making the compiler complex, it would be great if it
could deduce the legal set of types used by each argument in each
function (perhaps using supplied predicate guards such as record/2), and
complain at compile-time if it is ever violated in a subsequent function
call.  I believe this would be the simplest way to apply "strong typing"
to Erlang (since it doesn't change any existing semantics of the
language (except for those semantics that are already illegal anyway!))
There's no way it could catch all type errors (send, fun, etc.) but it
would be a great step in that direction.

And that's basically *it*.  I'm a total cynic and I can only find
*three* flaws in Erlang that are serious enough to annoy me.  This
language is a thing of beauty!

There are other "features" I have in mind but I can tell that they are
far enough beyond the scope of Erlang that adding them would result in a
new, descendant language.  This leads me to several questions, though.

Just how "discouraged" is "discouraged" when it comes to Parse
Transformations?  Is there any example code for a Parse Transformation?
(I imagine not, since it's discouraged.)  I can see why this would be,
Erlang is supposed to be a fairly predictable language.  Playing with
the grammar makes it less predictable, and thus should be avoided.  I
don't wholly disagree.

But say I want to build my own numeric data type.  Naturally my source
will be more readable if I can overload the arithmetic operations +-*/
on it - could I express this as a parse transformation?  Basically I'd
be representing a numeric datum as a tuple, so I'd want to be able to
translate {1, etc} + {2, etc} into mynumeric:add({1, etc}, {2, etc}).

I'd be very interested in any feedback!

_chris

--
Change rules.
Share and Enjoy on Cat's Eye Technologies' Electronic Mailing List
http://www.catseye.mb.ca/list.html


Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Thomas Lindgren-2

I got on a roll here :-)

cpressey wrote:
> I understand this makes the compiler more complex though, but even under
> the current system, more BIFs should be allowed in guards.  I don't see
> why I can't say "when element(N, T) == 0", for example; that's fairly
> lightweight, no?

Guards should basically be side-effect free and guaranteed to
terminate. Note that element/2 is permitted in guards (see p.29 of the
Erlang book).

> 3. Speaking of making the compiler complex, it would be great if it
> could deduce the legal set of types used by each argument in each
> function (perhaps using supplied predicate guards such as record/2), and
> complain at compile-time if it is ever violated in a subsequent function
> call.  I believe this would be the simplest way to apply "strong typing"
> to Erlang (since it doesn't change any existing semantics of the
> language (except for those semantics that are already illegal anyway!))
> There's no way it could catch all type errors (send, fun, etc.) but it
> would be a great step in that direction.

Unfortunately, type checking of function calls can be difficult in
general. For example, hot code loading means remote calls (between
modules) do not have a statically known type. Take the following
definition:

f(X) -> m:g(X).

Since m can be loaded at runtime, you can't say anything about what
type X is expected to be. (In fact, it can change as new versions of m
are loaded.) So you can't statically derive a type for f/1.

There are a number of other problems as well. For example, what _is_ a
'type' in Erlang? In ordinary Hindley-Milner type inference, type
constructors can't overlap: you thus know that F(x1,...,xn) always
belongs to type T. But Erlang programs are not structured that way;
for example, should we separate a tuple {data,X1,X2} from a tuple
{A1,A2,A3} (where A1 could happen to be the atom 'data')? And there are
some other problems as well.

Despite my being a wet blanket above, there has been some work on type
checking Erlang. Joe Armstrong and Thomas Arts worked on it at CSLab,
as did Wadler and Marlow. Ander Lindgren did a Masters on
it. Sven-Olof Nystr?m has had a stab more recently (I've even tested
his code a bit :-).

(Thomas Arts now works on verification of Erlang, which may be a more
fruitful track, since it attacks the truly hair-tearingly difficult
bugs.)


Anyway, your message inspired me to look for more dark corners to be
swept. Here are two:

1. I think records should be cleaned up. The underlying
tuple-representation shines through too brightly, and the current
operations are too weak (e.g., switching on record type is
awkward). Not to mention that include-files are required. To be fair,
a better definition is a bit tricky with hot code loading. (There are
a couple of proposals being discussed; one by Richard O'Keefe seems to
be the favorite.)

2. Another vast and poorly lit area is that of exceptions. Today,
there is seldom any way of knowing what sorts of exceptions a function
can throw, and the exceptions are usually uninformative (e.g.,
"badmatch" when you have half a dozen pattern matches in the indicated
function). Thus, _using_ the exit reason to recover from an error is
difficult. In practice, you print, log or ignore an exit today.

I think we should take better advantage of exceptions than we do
today.

> Is there any example code for a Parse Transformation?  (I imagine
> not, since it's discouraged.)

I think Mnemosyne uses parse transforms.

                       Thomas
--
Thomas Lindgren thomas+junk
Alteon WebSystems


Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Luke Gorrie-3
In reply to this post by Chris Pressey
Chris Pressey <cpressey> writes:

> Just how "discouraged" is "discouraged" when it comes to Parse
> Transformations?  Is there any example code for a Parse Transformation?
> (I imagine not, since it's discouraged.)  I can see why this would be,
> Erlang is supposed to be a fairly predictable language.  Playing with
> the grammar makes it less predictable, and thus should be avoided.  I
> don't wholly disagree.

Tobbe's "xx-1.0" on the User Contribution page at erlang.org has a
parse transform module which you can read as an example of how to
write them.

Cheers,
Luke



Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Robert Virding-4
In reply to this post by Chris Pressey
Chris Pressey <cpressey> writes:
>
>1. Having semicolons as statement seperators instead of statement
>terminators is basically *annoying* during program maintainance (while
>adding or removing a statement.)  Having an extraneous semicolon
>(especially before an 'end' token) generate a warning rather than an
>error would be more pleasant.

To be consistent you would have to allow it before the final '.' of a
function definition as well.  Personally I still don't see the problem.
:-)

>2. I don't think I agree with the idea that only some "specially
>selected" functions should be allowed in guards.  This makes guards
>semantically unorthogonal.  I think guards should be thought of as mere
>syntactic sugar, and the compiler will choose what it determines to be
>the most efficient manner of implementing the guard.  This would help in
>writing more readable programs.

The main criteria is that guard must be side-effect free and guaranteed
to terminate.  They should preferably also be bounded in time.

>Just how "discouraged" is "discouraged" when it comes to Parse
>Transformations?  Is there any example code for a Parse Transformation?
>(I imagine not, since it's discouraged.)  I can see why this would be,
>Erlang is supposed to be a fairly predictable language.  Playing with
>the grammar makes it less predictable, and thus should be avoided.  I
>don't wholly disagree.

That was probably a mistake to explicitly discourage parse transforms.
The reason was to keep the syntax predictable but there are some pretty
neat things you could do.  One thought I have had would be to do a much
smarter macro package.

>But say I want to build my own numeric data type.  Naturally my source
>will be more readable if I can overload the arithmetic operations +-*/
>on it - could I express this as a parse transformation?  Basically I'd
>be representing a numeric datum as a tuple, so I'd want to be able to
>translate {1, etc} + {2, etc} into mynumeric:add({1, etc}, {2, etc}).

You could express this as a parse transform but you might run into
trouble with typing.  How do you detect that you really want/need to
overload a +-*/ ?

        Robert




Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Chris Pressey
In reply to this post by Thomas Lindgren-2
Thanks to everyone who responded.

Thomas Lindgren wrote:
> I got on a roll here :-)
> cpressey wrote:
> > I understand this makes the compiler more complex though, but even under
> > the current system, more BIFs should be allowed in guards.  I don't see
> > why I can't say "when element(N, T) == 0", for example; that's fairly
> > lightweight, no?
> Guards should basically be side-effect free and guaranteed to
> terminate. Note that element/2 is permitted in guards (see p.29 of the
> Erlang book).

Whoops, you're right.  I jumped the gun here.  My test code that
"showed" that element/2 didn't work in a guard, was actually broken in a
different way.  My bad :-)

> > 3. Speaking of making the compiler complex, it would be great if it
> > could deduce the legal set of types used by each argument in each
> > function (perhaps using supplied predicate guards such as record/2) [...]
> Unfortunately, type checking of function calls can be difficult in
> general. For example, hot code loading means remote calls (between
> modules) do not have a statically known type. Take the following
> definition:
> f(X) -> m:g(X).
> Since m can be loaded at runtime, you can't say anything about what
> type X is expected to be. (In fact, it can change as new versions of m
> are loaded.) So you can't statically derive a type for f/1.

That's OK, I don't believe in type inference, really; I think it's too
fancy to be warranted in many languages, and I think Erlang is one of
those languages.  If I define a (static, low-order) function (in this
module) like this:

  foo(X) when atom(X)  -> m:g(X);
  foo(X) when tuple(X) -> n:q(X).

Then later I go and do something unwise like

  foo([a,2,3]);

The compiler could (in this very limited instance) check that 'list' is
not a member of the set 'atom, tuple' and reject that call (or at least
produce a warning).  If the guards were left out, then the compiler
would have no way to check, and no warnings would appear (which would be
like Erlang as it is now.)

Now, this isn't some fancy inferred type scheme that tries to predict
everything; it's more like explicit typing from Pascal (which does allow
overlapping types; e.g. range types.)  The important property, that you
can build a "strong" sense of identity (checked at compile-time),
underlies both systems.

(You see, I appreciate and understand functional programming, but I'll
never be an FP zealot, so I'm not sold on the FP school's ideas about
type systems.  In my experience the really useful languages are the ones
that combine features from more than one heritage and/or paradigm.  Like
Erlang!)

I'd never propose trying to take the dynamic type system out of Erlang -
but supplementing it with a compile-time type checker of any sort would
surely increase productivity, as potential errors could be caught
without having to test the part of the program which contains the error.

> Anyway, your message inspired me to look for more dark corners to be
> swept. Here are two:
> 1. I think records should be cleaned up.

I realize records in Erlang leave a bit to be desired.  I find it mildly
annoying that I have to re-state the name of the record pretty much
everywhere.  I would probably find that less annoying if I were a
'Hungarian names' fan, but I'm not.  Otherwise, include files for
sharing structure declarations, transparency through to the underlying
tuple, etc, are suboptimal, but also for the most part adequate, on par
with other production languages.

> I think we should take better advantage of exceptions than we do
> today.

I was considering trapping a "badarith" exception to try to accomplish
overloading of +-*/, but that sounds like it's probably the wrong way to
approach that one.

Robert Virding wrote:
> Chris Pressey <cpressey> writes:
> >1. Having semicolons as statement seperators instead of statement
> >terminators is basically *annoying* during program maintainance (while
> >adding or removing a statement.)  Having an extraneous semicolon
> >(especially before an 'end' token) generate a warning rather than an
> >error would be more pleasant.
> To be consistent you would have to allow it before the final '.' of a
> function definition as well.  Personally I still don't see the problem.
> :-)

Sure, what the heck;.  :-)

The other option would be to make semicolons entirely optional - but I
thought that might be a bit risque for Erlang (even though I'm sure the
compiler and most humans could handle a semicolon-free source file with
few problems.)

> >2. I don't think I agree with the idea that only some "specially
> >selected" functions should be allowed in guards.  This makes guards
> >semantically unorthogonal.  I think guards should be thought of as mere
> >syntactic sugar, and the compiler will choose what it determines to be
> >the most efficient manner of implementing the guard.  This would help in
> >writing more readable programs.
> The main criteria is that guard must be side-effect free and guaranteed
> to terminate.  They should preferably also be bounded in time.

OK, I've been thinking about it.  To me, the main criteria for a guard
is that it *establishes the identity of the arguments* apropos to
pattern-matching.  Naturally, the methodology for establishing the
identity of something should be side-effect free and guaranteed to
terminate!

I've noticed that most high-level language are made up of several
sublanguages.  Most languages have a "type system sublanguage": in
Erlang guards serve this purpose, but the typing theme seems to go
almost unspoken (in the docs I've read so far.)

Most languages' type sublanguages are guaranteed to have certain
properties - referential transparency is popular, so that specific types
(or sets of types) can be predicted at compile-time.

> >But say I want to build my own numeric data type.  Naturally my source
> >will be more readable if I can overload the arithmetic operations +-*/
> >on it - could I express this as a parse transformation?  Basically I'd
> >be representing a numeric datum as a tuple, so I'd want to be able to
> >translate {1, etc} + {2, etc} into mynumeric:add({1, etc}, {2, etc}).
> You could express this as a parse transform but you might run into
> trouble with typing.  How do you detect that you really want/need to
> overload a +-*/ ?

Good point; a record would of course be better here.

If you don't care why I want to do this, you can stop reading here...
but for the interested, my beef with Erlang - in fact nigh all modern
programming languages - is that they enforce unnatural abstractions on
the programmer.  My case in point is if you say something like

  A = 3

This simple-looking statement doesn't actually correlate to the
real-world very well, because in the real world the number 3 could mean
a great many things.  3 meters?  3 feet?  300% of something else?  3
system failures?  3 stray cats?  The programmer simply did not say.  If
the programmer did not bother to add a comment to clarify it, it might
not be apparent from the source code, either.

In other words, in real-world models, scalar numbers and units of
measurement have a certain parity that should not be split; also, the
most maintainable programs in the production sphere are the ones that
are closest to their real-world models.

Therefore, one of the most valuable things a production language can
support is a way to store numbers with units of measurement.  But almost
no language does this.  I once worked on writing my own language to
serve this purpose, but it is simply too much work to write an entire
full-featured language for such a small (but critical) concept.  I began
implementing the concept as a numeric type in languages such as Perl.
Wherein it seems to work fine, but Perl has been proving to be, er, not
the greatest language for real-world models in general.

Erlang strikes me as being a much better fit.  Partly because of a
reason mentioned earlier on, the fact that Erlang types are dynamic, and
can overlap: both properties are required to leverage units of
measurement fully (I've done some experiments with strong typing and
units of measurement, and the results are less than spectacular.)

_chris

--
Change rules.
Share and Enjoy on Cat's Eye Technologies' Electronic Mailing List
http://www.catseye.mb.ca/list.html


Reply | Threaded
Open this post in threaded view
|

Constructive criticism of Erlang

Shawn Pearce
In reply to this post by Robert Virding-4
Robert Virding <rv> scrawled:

> Chris Pressey <cpressey> writes:
> >Just how "discouraged" is "discouraged" when it comes to Parse
> >Transformations?  Is there any example code for a Parse Transformation?
> >(I imagine not, since it's discouraged.)  I can see why this would be,
> >Erlang is supposed to be a fairly predictable language.  Playing with
> >the grammar makes it less predictable, and thus should be avoided.  I
> >don't wholly disagree.
>
> That was probably a mistake to explicitly discourage parse transforms.
> The reason was to keep the syntax predictable but there are some pretty
> neat things you could do.  One thought I have had would be to do a much
> smarter macro package.
>
> >But say I want to build my own numeric data type.  Naturally my source
> >will be more readable if I can overload the arithmetic operations +-*/
> >on it - could I express this as a parse transformation?  Basically I'd
> >be representing a numeric datum as a tuple, so I'd want to be able to
> >translate {1, etc} + {2, etc} into mynumeric:add({1, etc}, {2, etc}).
>
> You could express this as a parse transform but you might run into
> trouble with typing.  How do you detect that you really want/need to
> overload a +-*/ ?


I can see value in having +-*/ overloaded to some extent on a tuple,
as we have two issues -- one is the same as Chris Pressey, we want to
be able to keep unit information attached to the values being
manipulated -- but the other is that Erlang really doesn't have a full
numeric tower and therefore cannot operate as precisely as we'd like
with decimal values.

I wonder if erts could be extended to recognize that when it
gets two tuples as the arguments of a math operator that it uses the
first element of the first tuple as a package name, and calls a function
from that package to deal with the mess:

        {useng, 12, 3} + {useng, 6, 2}

....

-module(useng).

add({useng, AFt, AIn}, {useng, BFt, BIn}) ->
        RIn = AIn + BIn,
        if
                RIn >= 12 ->
                        {useng, AFt + BFt + 1, RIn - 12};
                RIn < 12 ->
                        {useng, AFt + BFt, RIn};
        end.

The other approach might be that there is some sort of table in the
emulator that binds the functions and types.  For instance, create an
ets table that holds 2-tuples of the form:

        {{a_type, b_type}, {module, function}}

where a_type and b_type are the first elements of the tuples given to
the math operator.  erts just does an ets table lookup for the key
{a_type, b_type} and gets back the module and function to invoke via
apply to handle the arith op.

Yes, its slower than doing the math directly in the function, but it
does let us poor users develop our own math packages for complex
numbers.

Of course, it also would let us add things that aren't really numeric...
which some might say adding two records together (as I do above) isn't
numeric....

--
Shawn.

  ``If this had been a real
    life, you would have
    received instructions
    on where to go and what
    to do.''