Strengths of Erlang as a declarative language (was Re: Compiler bug)

classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|

Strengths of Erlang as a declarative language (was Re: Compiler bug)

Chris Pressey
James Hague wrote:
>Chris Pressey wrote:
>>In some ways, Erlang has always struck me as a bizarre combination of
>>extremely low-level (assembler-like) concepts and extremely high-level
>>(lambda-calculus-like) concepts.
>
>I don't agree.  If you look at how pattern matching works in Haskell and
the
>ML family of languages, it is limited compared to what Erlang offers.  You
>can't repeat a variable, for example:
>
>first_two_are_same([X,X|_]) -> true.
>
>In general, you have to use classic if..then..else statements more often in
>these languages, because the pattern matching and guards are _more_
>restrictive than in Erlang.

I don't disagree, and I don't think it diminishes my point... perhaps it
would have been more appropriate to have said
"declarative-logic-(ProLog)-like" instead of ^-c-like.  (Except that Erlang
is still a far cry from ProLog in that there is no backtracking and there
are quite a few more restrictions on source code order... all in all this is
not a terrible thing, in fact it's what attracts me to it as a (nearly)
general purpose language.)

My point being that one of Erlang's strengths is expressivity, so I don't
think it should be unnaturally limited by arbitrary restrictions on guard
clauses... (that, and evaluation order so strictly following source code
order, are what strike me as the lowest-level almost "imperative"-like
features of Erlang.)

While I'm on the subject of language criticism... more and more I am finding
myself disagreeing with the canon law that message passing ought to be
encapsulated in function calls.  I agree that it ought to be *encapsulated*
for the sake of abstraction, but I believe that a function is the wrong
thing to encapsulate it in!  Functions traditionally do not have side
effects.  If I have a piece of Erlang code like

  Z = some_function(X,Y).

then I cannot immediately tell if there are side effects involved.  There
may or may not be, and it might be appropriate to assume that there
"shouldn't" be.  However, if I have a piece of Erlang code like

  some_server ! {some_function, self(), [X,Y]},
  Z = receive
    Any -> Any
  end.

then I can *immediately* tell at a glance that there *are* side effects
involved, I'm not left wondering and relying on my iffy neural-net memory or
hunting through source code.

Of course, a good naming convention accomplishes the same goal.

Chris




Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Richard Carlsson-4

Hi everybody! I'll try to give some answers to the questions asked in
the latest Erlang language debate. (This got rather long; be warned.)


Following the "Compiler bug?" confusion, Thomas Lindgren asked:

> Perhaps it is time to raise the question whether there are
> getting to be too many similar operations in Erlang as well?
>
> For guard operations, we have type(X) and is_type(X). (Could
> this be unified into just type(X)?)

Originally, the only builtin type tests were on the form
"integer(X)" etc., and these could only be accessed in guards
which effectively have a separate name space, so you could not
write:

        Bool = integer(X)

anywhere outside a guard. Indeed, you cannot even use them at the
"guard expression" level, only the "guard test" top level, so the
following clause is not accepted:

        X when integer(X) == true -> ...

because integer(X) is not a "guard BIF", only a "guard test". To
further complicate things, "float(X)", is also defined as a BIF -
even as a guard BIF, with completely different semantics
(int-to-float typecast). Assume that B contains a boolean value.
Then, in the following clause:

        X when B == float(X) ->

float(X) does not refer to the type test, but to the typecast
function.

To make things more uniform, all guard tests were given new
alternative names "is_...(X)", which are also proper BIFs/guard
BIFs, and may thus be used anywhere, both as guard tests and in
normal code. Like most BIFs, they belong to the 'erlang' module,

The old type tests must of course remain for backwards
compatibility, but they are now being renamed to the "is_" forms
very early in the compilation.


Back to Thomas:

> For guards, we have comma, semicolon (or), and, or, not.
> (Could we merge some operations here?)

This is still work in progress, more or less. Robert Virding
extended the guard syntax to allow 'and' for comma and 'or' for
semicolon, as well as allowing these operators in guard
expressions. 'not' should also be allowed on all levels. It seems
that there still is some bug in the implementation, however.

Here too, the reason for the extension is to make guards more like
other expressions. A complication is that failure in the first
argument of a top-level guard 'or' (semicolon) goes on to try the
other alternative as if there were two clauses, while at the
expression-level the whole 'or'-operation fails immediately.

> For expressions, we have and, or, andalso, orelse. (Do people
> use "E1 and E2" while relying on E2 being evaluated when E1 is
> false? If not, rename andalso into and, etc.)

For some reason, the original and/or were defined to evaluate both
arguments. Personally, I would love to give and/or the semantics
of andalso/orelse, but previous experience suggests that any
semantic changes in Erlang, however obscure, are politically
impossible. Could this be an exception to the rule?


Chris Pressey wrote:

- Allow ANY function in guard tests.
- Only WARN if the guard test does not operate in constant time.
- Deduce which functions operate in constant time by analysis

The main problem with allowing any function in guards is not
really the time (after all, length/1 is allowed), but the side
effects (such as sending messages or updating ETS tables). The
assumption when a guard fails is that the state of the world did
not change. Also, guards are used in 'receive': sending a message
from a guard expression in a receive-clause, or evaluating a
receive within a receive, could cause major inconsistencies (it
would be very hard to specify what the semantics actually should
be, if this was allowed).

Analysing an expression to tell whether it is side effect free is
not that hard, but note that even a call to another module is a
potential side effect (the module might even not be loaded yet).
So for user-defined functions, only local calls could be allowed.

Now, guards should be efficiently implemented: even if not always
taking constant time, the overhead should be small, so a full
backtracking mechanism like in Prolog is out of the question. The
current Beam implementation relies quite heavily on being able to
generate special code for guards, where many things can be
assumed:

        - no side effects can happen
        - exceptions only cause a jump to a "failure" label
        - all calls are to builtins; special calling conventions
          can be used.
        - any created data is not live outside the guard
        - (possibly other things...)

To put it briefly: allowing more general expressions in guards is
definitely something one would like to do, but it needs a lot of
work to make it both safe and efficient.

Chris also suggested:

> - Possibly have a module 'type' and place all the type-assertion
> functions in it (type:number(X), type:list(X), etc)

Structured module namespaces are just making their way into the
language, so you may see something like "erl.lang.term:is_list(X)"
in the future.

> As for short-circuiting, you shouldn't have to think about it in
> referentially transparent code, and (for me at least) it's
> fairly rare that I have to think about it in side-effectful code
> either, so I'm not sure why orelse and andalso were introduced,
> when two seperate tests in the code is more explicit and
> possibly clearer.

Of course, if the compiler can decide that the RHS of an 'and' is
ref. transparent *and* type-safe (i.e., can't cause an exception),
it can generate short-circuit code. But this is often not possible
(outside guards). Using andalso/orelse let you express that you
know what you are doing.

Having two separate tests is not clearer. Compare e.g.:

        is_string([X | Xs]) ->
            is_char(X) andalso is_string(Xs);
        is_string([]) ->
            true.
to:
        is_string([X | Xs]) ->
            case is_char(X) of
                true ->
                    is_string(X);
                false ->
                    false
            end.
        is_string([]) -> true.

With the explicit test, you have to make sure you get the
true/false cases right (which gets error prone if there is more
than one level) when writing it, and furthermore, someone reading
your code must ask him/herself "what does this (nested) switch
here really implement?"


Chris also wrote:

> more and more I am finding myself disagreeing with the canon law
> that message passing ought to be encapsulated in function calls.
> I agree that it ought to be *encapsulated* for the sake of
> abstraction, but I believe that a function is the wrong thing to
> encapsulate it in! Functions traditionally do not have side
> effects. If I have a piece of Erlang code like
>
>   Z = some_function(X,Y).
>
> then I cannot immediately tell if there are side effects
> involved. [...] However, if I have a piece of Erlang code like
>
>   some_server ! {some_function, self(), [X,Y]},
>   Z = receive
>     Any -> Any
>   end.
>
> then I can *immediately* tell at a glance that there *are* side
> effects involved,

I agree: there should (in a better world) be another kind of
abstraction for this purpose. The real issue is not that the
send/receive are explicit in the code - because they often ought
to be - but that they expose the data structures used in the
message passing (or to be specific, the 'receive' does). A way to
handle this would be to have abstract patterns (O'Keefe 1998), but
from what I have heard, implementing this idea in an efficient way
turned out to be a lot more difficult than expected.


Hal Snyder asked:

> Which reminds me, I wish Erlang had elsif, or cond. Did anything
> come of http://www.bluetail.com/~rv/Erlang-spec/Proposals/cond.shtml?

This is on its way. In preparation, 'cond' was made a reserved
word in R8, along with 'try'. (Adding a new keyword is a fairly
large language change, so people should be duly warned.)


I hope that about covers it. Thank you for your time.

        /Richard



Richard Carlsson (richardc)   (This space intentionally left blank.)
E-mail: Richard.Carlsson WWW: http://www.csd.uu.se/~richardc/
 "Having users is like optimization: the wise course is to delay it."
   -- Paul Graham



Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Maurice Castro
Richard Carlsson (richardc)  wrote:
>
> For some reason, the original and/or were defined to evaluate both
> arguments. Personally, I would love to give and/or the semantics
> of andalso/orelse, but previous experience suggests that any
> semantic changes in Erlang, however obscure, are politically
> impossible. Could this be an exception to the rule?
>

In languages that have to work in real time, the passing of time
is a side effect. Short circuit logic evaluation makes the evaluation
time of a function less consistent and hence can be undesirable.

        Maurice Castro


Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Chris Pressey
In reply to this post by Richard Carlsson-4
Sorry that this is an equally long (actually, longer!) response to a long
message... it's a topic that is of great interest to me :)

On Mon, 15 Apr 2002 16:23:34 +0200 (MET DST)
Richard Carlsson <richardc> wrote:

> For some reason, the original and/or were defined to evaluate both
> arguments. Personally, I would love to give and/or the semantics
> of andalso/orelse, but previous experience suggests that any
> semantic changes in Erlang, however obscure, are politically
> impossible. Could this be an exception to the rule?

With any production language, retaining backwards-compatibility is
important, some might even say crucial.

Languages are never designed 100% correctly the first time of course, such
an expectation would be unrealistic.  But once an imperfect language is in
use, it has to stay imperfect... one might say that it experiences
'language pollution' after a time.  Things like erlang:hash/1, the lib
module, the orddict module, the assumption that ports are synonymous with
named pipes, etc, are all deprecated and in theory will disappear someday
- but in practice, it seems likely they won't, because they can't without
breaking someone's old code somewhere.  It's somewhat akin to archaic
words in spoken language.  Very few people use the word 'wherefore' in
conversation these days, but if you don't know the word 'wherefore', you
can't enjoy Shakespeare!

On the other hand, if all you want to do is enjoy Shakespeare, you don't
need to know the word 'microwave'...

Back in terms of programming languages - sometimes the pressure from
language pollution encourages people to design a fresh new language which
discards the 'mistakes' of the old language while retaining all the things
it got 'right'.  Java, over C++, might be an example of that.  I doubt
that there is enough pressure currently to completely redesign and migrate
to a "Rational Erlang", but one could also say that it's only a matter of
time.  (That's kind of a rhetorical excuse, though.)

> Chris Pressey wrote:
>
> - Allow ANY function in guard tests.
> - Only WARN if the guard test does not operate in constant time.
> - Deduce which functions operate in constant time by analysis
>
> The main problem with allowing any function in guards is not
> really the time (after all, length/1 is allowed),

Well, this is beside the point, and splitting hairs too, but is it written
in stone that length/1 must operate in O(n) time?  The length of a list -
as well as its tail pointer - could be cached for quick retrieval, and
this could make some list operations quicker (at the cost of other
operations)

Whether this would be 'breaking backwards-compatibility' or not is a tough
question, though.  Generally I'd say no, but for Erlang, being very
sensitive to execution performance, I might make an exception.

> but the side
> effects (such as sending messages or updating ETS tables). The
> assumption when a guard fails is that the state of the world did
> not change.

Very true.  Side-effects in a guard would be very bad programming practice
- usually a dire mistake, and when not, then certainly an awkward and
objectionable coding style.

But, Erlang treats this almost as a syntactic rule, rather than a semantic
one - the syntax of a guard expression disallows side-effects.  This seems
a bit strict - very understandable, considering the origins of the
language, but a bit strict nonetheless.  Reformulating it as a semantic
rule would be a bit nicer, in my mind.

> Also, guards are used in 'receive': sending a message
> from a guard expression in a receive-clause, or evaluating a
> receive within a receive, could cause major inconsistencies (it
> would be very hard to specify what the semantics actually should
> be, if this was allowed).

Absolutely.  Again, my only problem with it is that the restrictions are
more syntax-based than semantics-based.

On a related note, is there a good reason for 'receive' to be a language
structure, rather than a BIF?  For example, instead of

  receive
    {foo, X} -> bar(X);
    {baz, Y} -> quuz(Y)
  end

couldn't one say, with identical intent,

  Msg = receive(),
  case Msg of
    {foo, X} -> bar(X);
    {baz, Y} -> quuz(Y)
  end

Is the reason that it's a language structure, that the compiler can
more easily generate better code (something like Linda?)

> Analysing an expression to tell whether it is side effect free is
> not that hard, but note that even a call to another module is a
> potential side effect (the module might even not be loaded yet).
> So for user-defined functions, only local calls could be allowed.

And that diminishes its usefulness - as it would be desirable to put
user-defined guards in a common, reusable module - but I can see why,
given the ability to update code while it's running.

I'm tempted to see module loading as a special case of side-effect,
though.  It should only happen once for each time the code is updated,
which would not be a common occurance.

> Now, guards should be efficiently implemented: even if not always
> taking constant time, the overhead should be small, so a full
> backtracking mechanism like in Prolog is out of the question. The
> current Beam implementation relies quite heavily on being able to
> generate special code for guards, where many things can be
> assumed:
>
> - no side effects can happen
> - exceptions only cause a jump to a "failure" label
> - all calls are to builtins; special calling conventions
>  can be used.
> - any created data is not live outside the guard
> - (possibly other things...)
>
> To put it briefly: allowing more general expressions in guards is
> definitely something one would like to do, but it needs a lot of
> work to make it both safe and efficient.

I see...  I tend to see the most elegant path to achieving this, to be
rigorous analysis and optimization on the compiler's part.  But this
definately has drawbacks, not the least of which is the complexity of the
compiler, and long compile times.

> Chris also suggested:
>
> > - Possibly have a module 'type' and place all the type-assertion
> > functions in it (type:number(X), type:list(X), etc)
>
> Structured module namespaces are just making their way into the
> language, so you may see something like "erl.lang.term:is_list(X)"
> in the future.

Oddly, I've never been too concerned that Erlang has such a 'flat'
namespace structure.  I think a deep heirarchy would be even worse.
Unless perhaps there was some sort of 'search path'-like mechanism for
resolving names.

> > As for short-circuiting, you shouldn't have to think about it in
> > referentially transparent code, and (for me at least) it's
> > fairly rare that I have to think about it in side-effectful code
> > either, so I'm not sure why orelse and andalso were introduced,
> > when two seperate tests in the code is more explicit and
> > possibly clearer.
>
> Of course, if the compiler can decide that the RHS of an 'and' is
> ref. transparent *and* type-safe (i.e., can't cause an exception),
> it can generate short-circuit code. But this is often not possible
> (outside guards). Using andalso/orelse let you express that you
> know what you are doing.
>
> Having two separate tests is not clearer. Compare e.g.:
>
> is_string([X | Xs]) ->
>    is_char(X) andalso is_string(Xs);
> is_string([]) ->
>    true.
> to:
> is_string([X | Xs]) ->
>    case is_char(X) of
>        true ->
>            is_string(X);
>        false ->
>            false
>    end.
> is_string([]) -> true.
>
> With the explicit test, you have to make sure you get the
> true/false cases right (which gets error prone if there is more
> than one level) when writing it, and furthermore, someone reading
> your code must ask him/herself "what does this (nested) switch
> here really implement?"

Perhaps this is a strange way to approach it, but it seems to me that
Erlang's evalutation-order-closely-follows-source-code-order feature could
be exploited to write a fairly clear, non-nested version of this as:

  is_string([X | Xs]) when not is_char(X) -> false;
  is_string([X | Xs]) when not is_string(Xs) -> false;
  is_string(X) when is_list(X) -> true.

On the topic of type-safeness... I am somewhat confounded by the fact that
all errors are treated equally.  This may be somewhat difficult to
explain, but I don't feel that a type error and (e.g.) a file-not-found
error are of the same gravity.  What I am doing more and more often in my
code is writing 'wrapper' functions around BIF's that throw errors.  One
example of this is list_to_integer/1.  Often I'm in a position where I
want to convert a string to an integer, even if it isn't a legal integer.
I ended up writing a function like:

  my_list_to_integer(List, Default) ->
    case catch list_to_integer(List) of
      X when is_integer(X) -> X;
      _ -> Default
    end.

Some might say that this comes from ingrained habit of working with
languages with bad error-handling capabilities, and that may be true.  But
then again, string:str/2 doesn't throw an error when the substring is not
found, so... is it really 'wrong'?

(Also it would be more consistent for the indexes in GS stuff to be
1-based, not 0-based, but that is really digressing from the point...)

>
> Chris also wrote:
>
> > more and more I am finding myself disagreeing with the canon law
> > that message passing ought to be encapsulated in function calls.
> > I agree that it ought to be *encapsulated* for the sake of
> > abstraction, but I believe that a function is the wrong thing to
> > encapsulate it in! Functions traditionally do not have side
> > effects. If I have a piece of Erlang code like
> >
> >   Z = some_function(X,Y).
> >
> > then I cannot immediately tell if there are side effects
> > involved. [...] However, if I have a piece of Erlang code like
> >
> >   some_server ! {some_function, self(), [X,Y]},
> >   Z = receive
> >     Any -> Any
> >   end.
> >
> > then I can *immediately* tell at a glance that there *are* side
> > effects involved,
>
> I agree: there should (in a better world) be another kind of
> abstraction for this purpose.

One such thing I thought of - not particularly elegant - is a 'relay
process', a process whose entire purpose is simply to pass any messages it
receives, to another process - possibly translating them while in transit.
The translation provides a place for the abstraction to happen.

But this seems, at the very least, somewhat wasteful.

> The real issue is not that the
> send/receive are explicit in the code - because they often ought
> to be - but that they expose the data structures used in the
> message passing (or to be specific, the 'receive' does). A way to
> handle this would be to have abstract patterns (O'Keefe 1998), but
> from what I have heard, implementing this idea in an efficient way
> turned out to be a lot more difficult than expected.

It might not be necessary to go that far (but abstract patterns might
clear up a lot of other issues as well) - I think mainly it's a matter of
breaking the habit of thinking in terms of what is being sent as data.  Or
'raw' data, if you like.  A function can be thought of in terms of 'raw'
data - it is an atom or two, for the module and function name, and a list
of terms, for the arguments.  But we don't generally think of it this way,
for reasons of abstraction (although we DO think of it this way when using
spawn/3, but never mind that! :)  If messages could be thought of as
similarly consistently 'packaged' data, that might help matters a bit,
without going all the way into abstract patterns.

Considering spawn/3, and the syntax used in ets:match, I would say that
Erlang is a bit weak when it comes to reflectivity - being able to
describe Erlang code in Erlang.  Ideally the syntax for spawn'ed functions
would closely match that of non-spawn'ed functions, and the syntax for
ets:match'ing would closely resemble that for regular case ... of ... end
matching.  Abstract patterns seem to hold a comprehensive, if somewhat
drastic (overkill) solution, to all these things, and more (like macros.)

My, I do tend to ramble on, don't I?  Sorry about that :)

Chris


Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Thomas Lindgren-3
In reply to this post by Richard Carlsson-4

Lots of issues here, hope I got them all :-)

[new and old type tests]
>The old type tests must of course remain for backwards
>compatibility, but they are now being renamed to the "is_" forms
>very early in the compilation.

I think my basic question is whether one can rename guard tests
into the new operations directly. This way, one could get rid
of having both type(X) and is_type(X). It would seem to be possible
for most cases, perhaps excepting the dual mode of float(X) that
you mention, or any similar cases.

(Though looking at the context, it should be possible to deduce
what version is intended: off the top of my head, on the toplevel
one uses is_float(X), inside arithmetic one uses to_float(X) or
whatever the operation may be called. Any questionable cases are
flagged with a warning.)

> /.../ the reason for the extension is to make guards more like
> other expressions. A complication is that failure in the first
> argument of a top-level guard 'or' (semicolon) goes on to try the
> other alternative as if there were two clauses, while at the
> expression-level the whole 'or'-operation fails immediately.

I think making guards behave like expressions is a Good Thing,
generally speaking, because it is more convenient to have full
boolean expressions. As you say, the problem is nailing down the details.

I suppose the behaviours above should be unified. If nothing else, for
the sanity of programmers :-)

[boolean expressions]
>For some reason, the original and/or were defined to evaluate both
>arguments. Personally, I would love to give and/or the semantics
>of andalso/orelse, but previous experience suggests that any
>semantic changes in Erlang, however obscure, are politically
>impossible. Could this be an exception to the rule?

One might hope so, if only for the reason that most people might
think 'and' really works like 'andalso'. And that is why I
asked the question: do you people out there really want 'and' to evaluate
both arguments?

(Personally, I tend to curse the fact that it does :-)

[deep guards]
>The main problem with allowing any function in guards is not
>really the time (after all, length/1 is allowed), but the side
>effects (such as sending messages or updating ETS tables). The
>assumption when a guard fails is that the state of the world did
>not change. Also, guards are used in 'receive': sending a message
>from a guard expression in a receive-clause, or evaluating a
>receive within a receive, could cause major inconsistencies (it
>would be very hard to specify what the semantics actually should
>be, if this was allowed).

To handle this, I once proposed that execution would be in 'normal mode' or
'guard mode'. The latter would trigger an un-catchable exception
(i.e., fail) when a side-effect was attempted. That way, remote
calls still work. (The originating guard would catch the failure.)

(Roughly speaking, you would only switch out of guard mode when you
exit the guard that set the computation into guard mode.)

I'm a bit ambivalent about permitting "deep guards", however.

What do we define as a side-effect, for instance? _Reading_ the database?
Permitting long-running guards means we must be able to context-switch
inside the guard, which means the database can change while we run the
guard, which might be construed as an impurity. (And what about read locks?)
And so on.

I believe the original restriction on guards comes from concurrent logic
programming, where "deep guards" had severe problems and ultimately
led to disallowing them.

Well, the Real World intrudes so I have to leave off here. Basically,
I'd like to reduce the number of constructs when their meanings are
very close. And yes, legacy code is a problem.

-- Thomas




Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Fredrik Linder-2
> Thomas Lindgren:
> [new and old type tests]
> >The old type tests must of course remain for backwards
> >compatibility, but they are now being renamed to the "is_" forms
> >very early in the compilation.
>
> I think my basic question is whether one can rename guard tests
> into the new operations directly. This way, one could get rid
> of having both type(X) and is_type(X). It would seem to be possible
> for most cases, perhaps excepting the dual mode of float(X) that
> you mention, or any similar cases.
>
> (Though looking at the context, it should be possible to deduce
> what version is intended: off the top of my head, on the toplevel
> one uses is_float(X), inside arithmetic one uses to_float(X) or
> whatever the operation may be called. Any questionable cases are
> flagged with a warning.)
>
> > /.../ the reason for the extension is to make guards more like
> > other expressions. A complication is that failure in the first
> > argument of a top-level guard 'or' (semicolon) goes on to try the
> > other alternative as if there were two clauses, while at the
> > expression-level the whole 'or'-operation fails immediately.
>
> I think making guards behave like expressions is a Good Thing,
> generally speaking, because it is more convenient to have full
> boolean expressions. As you say, the problem is nailing down the details.
>
> I suppose the behaviours above should be unified. If nothing else, for
> the sanity of programmers :-)

I have always thought of Erlang as a make-it-easy-for-the-programmer type of
language, especially with respect to understandability and ease-of-use; only
one or two ways of doing the same thing, clear language constructs etc. But
lately I find that Erlang is drifting away from that, my thought, with
things like: andalso, the MatchSpec used in ets/mnesia, etc.

Please do not misinterpret my intension here, some or most of these changes
are very useful; it is the syntax of them that concerns me. For instance, I
cannot see why 'and', 'or' and 'not' could not be allowed in guards. This
syntax change(?) could still coexists with (;.), and have the same rules
applied to them. With 'and' as syntactic sugar  for ',', 'or' as syntactic
sugar for ';' and 'not' as suntactic sugar for 'false == ' (or something)
and possibly warning if (;,) is used, would I as a programmer only have one
boolean syntax to use.

> Thomas Lindgren:
> [boolean expressions]
> >For some reason, the original and/or were defined to evaluate both
> >arguments. Personally, I would love to give and/or the semantics
> >of andalso/orelse, but previous experience suggests that any
> >semantic changes in Erlang, however obscure, are politically
> >impossible. Could this be an exception to the rule?
>
> One might hope so, if only for the reason that most people might
> think 'and' really works like 'andalso'. And that is why I
> asked the question: do you people out there really want 'and' to evaluate
> both arguments?

No

> Thomas Lindgren:
> (Personally, I tend to curse the fact that it does :-)
>
> [deep guards]
> >The main problem with allowing any function in guards is not
> >really the time (after all, length/1 is allowed), but the side
> >effects (such as sending messages or updating ETS tables). The
> >assumption when a guard fails is that the state of the world did
> >not change. Also, guards are used in 'receive': sending a message
> >from a guard expression in a receive-clause, or evaluating a
> >receive within a receive, could cause major inconsistencies (it
> >would be very hard to specify what the semantics actually should
> >be, if this was allowed).
>
> To handle this, I once proposed that execution would be in 'normal mode'
or

> 'guard mode'. The latter would trigger an un-catchable exception
> (i.e., fail) when a side-effect was attempted. That way, remote
> calls still work. (The originating guard would catch the failure.)
>
> (Roughly speaking, you would only switch out of guard mode when you
> exit the guard that set the computation into guard mode.)
>
> I'm a bit ambivalent about permitting "deep guards", however.
>
> What do we define as a side-effect, for instance? _Reading_ the database?
> Permitting long-running guards means we must be able to context-switch
> inside the guard, which means the database can change while we run the
> guard, which might be construed as an impurity. (And what about read
locks?)
> And so on.
>
> I believe the original restriction on guards comes from concurrent logic
> programming, where "deep guards" had severe problems and ultimately
> led to disallowing them.
>
> Well, the Real World intrudes so I have to leave off here. Basically,
> I'd like to reduce the number of constructs when their meanings are
> very close. And yes, legacy code is a problem.

Could one way to go be to add a 'guard' construct for writing
guard-functions, that only may use other guard-functions?

guard my_guard(Arg1, Arg2) -> true | false.

Maybe with the extension of considering the guard construct as a catch - to
guard from exceptions?

Also maybe to consider all guards to belong to the same module 'guard', but
allow new guards to be defined in other modules (these should then be
updated when re-loading the original module):

-module(x).
-guards([my_guard/2]).
guard:my_guard(Arg1, Arg2) -> true | false.




Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Richard Carlsson-4
In reply to this post by Thomas Lindgren-3

On Tue, 16 Apr 2002, Thomas Lindgren wrote:

> [new and old type tests]
> >The old type tests must of course remain for backwards
> >compatibility, but they are now being renamed to the "is_" forms
> >very early in the compilation.
>
> I think my basic question is whether one can rename guard tests
> into the new operations directly. This way, one could get rid
> of having both type(X) and is_type(X). It would seem to be possible
> for most cases, perhaps excepting the dual mode of float(X) that
> you mention, or any similar cases.
>
> (Though looking at the context, it should be possible to deduce
> what version is intended: off the top of my head, on the toplevel
> one uses is_float(X), inside arithmetic one uses to_float(X) or
> whatever the operation may be called. Any questionable cases are
> flagged with a warning.)

You *can* do this, even today! (Shameless self-promotion follows.)

If you get my syntax_tools package (1.2) from erlang.org, there is a
module 'erl_tidy' (full documentation in doc/erl_tidy.html), which does
this and lots more to "modernise" old code - all nicely pretty-printed
with comments and preprocessor stuff preserved. A number of options are
available to control what things are changed.

If you just want to use it as a "lint"-thing, you can give it a flag
"test", and you'll get the reports without changing files.

It automatically updates known obsolete function calls to the new
functions, where possible (i.e., where arity and argument order are the
same). You can supply a list of additional functions to be redirected.

It can remove unused functions from the code, and expand function
imports so that all remote calls become explicit, plus some other nifty
transformations.

The 'dir' function works recursively through a directory tree.
If files are being created, backups of the original files are made.

I can't guarantee that it's totally bug free, but I have run it on the
*whole* Erlang/OTP distribution - every single .erl file in it - and
recompiled, and everything seems to work.

In a few cases, it's just too hard for it to read a particular file (if
weird macros are used), and then it just skips that file.

Go ahead and try it. If you come up with more things it should be able
to update, please tell me.


> > /.../ the reason for the extension is to make guards more like
> > other expressions. A complication is that failure in the first
> > argument of a top-level guard 'or' (semicolon) goes on to try the
> > other alternative as if there were two clauses, while at the
> > expression-level the whole 'or'-operation fails immediately.
>
> I think making guards behave like expressions is a Good Thing,
> generally speaking, because it is more convenient to have full
> boolean expressions. As you say, the problem is nailing down the details.
>
> I suppose the behaviours above should be unified. If nothing else, for
> the sanity of programmers :-)

Not to mention the sanity of compiler writers :-]

But this seems to be another of those things that just can't be changed.
Syntactically, we can and do allow and/or for ,/; - but the semantic
difference in guards between "top-level" conjunction and
"expression-level" conjunction will have to remain, I'm afraid. It's a
fairly subtle thing, and I don't think it will cause any real trouble.


> [deep guards]
> I'm a bit ambivalent about permitting "deep guards", however.
>
> What do we define as a side-effect, for instance? _Reading_ the
> database? Permitting long-running guards means we must be able to
> context-switch inside the guard, which means the database can change
> while we run the guard, which might be construed as an impurity.
> (And what about read locks?) And so on.
>
> I believe the original restriction on guards comes from concurrent
> logic programming, where "deep guards" had severe problems and
> ultimately led to disallowing them.

Thanks - that saved me from trying to explain this point. I also believe
that although we might be able to extend guards in Erlang somewhat, and
remove some strange syntactic limitations, we will probably never have
"deep guards" - they come with deep problems, especially with things
like context switching. (And a special syntax for declaring guard
functions does not fix this.)

        /Richard

Richard Carlsson (richardc)   (This space intentionally left blank.)
E-mail: Richard.Carlsson WWW: http://www.csd.uu.se/~richardc/
 "Having users is like optimization: the wise course is to delay it."
   -- Paul Graham



Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Ulf Wiger-4
On Wed, 17 Apr 2002, Richard Carlsson wrote:

>On Tue, 16 Apr 2002, Thomas Lindgren wrote:
>
>> [deep guards]
>> I'm a bit ambivalent about permitting "deep guards", however.
>>
>> What do we define as a side-effect, for instance? _Reading_ the
>> database? Permitting long-running guards means we must be able to
>> context-switch inside the guard, which means the database can change
>> while we run the guard, which might be construed as an impurity.
>> (And what about read locks?) And so on.
>>
>> I believe the original restriction on guards comes from concurrent
>> logic programming, where "deep guards" had severe problems and
>> ultimately led to disallowing them.
>
>Thanks - that saved me from trying to explain this point. I also
>believe that although we might be able to extend guards in
>Erlang somewhat, and remove some strange syntactic limitations,
>we will probably never have "deep guards" - they come with deep
>problems, especially with things like context switching. (And a
>special syntax for declaring guard functions does not fix this.)
>
> /Richard

In some way this is perhaps similar to the previous discussions
on match specifications, where posters, self() included, have
suggested a more erlang-like syntax.

http://www.erlang.org/ml-archive/erlang-questions/200010/msg00223.html

Basically, a fun() or similar could be used as a guard (or match
specification) iff it is obvious to the compiler that it is (1)
shallow, (2) pure.

/Uffe



Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Thomas Lindgren-3
In reply to this post by Richard Carlsson-4


From: Richard Carlsson [mailto:richardc]

> You *can* [rewrite guards, etc], even today! (Shameless self-promotion
follows.)

Well, why not do it, then? :-) In the compiler, you can do it after
macro preprocessing too, so the case of strange macros that you mention
subsequently does not occur.

It sounds like a nice tool, btw.

>But this seems to be another of those things that just can't be changed.
>Syntactically, we can and do allow and/or for ,/; - but the semantic
>difference in guards between "top-level" conjunction and
>"expression-level" conjunction will have to remain, I'm afraid. It's a
>fairly subtle thing, and I don't think it will cause any real trouble.

Subtle differences sound somewhat ominous to me; doubly so when there
is no good reason for them (apart from "backwards compatibility").
Indeed, this proliferation was why I raised the question to begin with.
(So we neatly return to the beginning again :-)

-- Thomas



Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Richard Carlsson-4

On Thu, 18 Apr 2002, Thomas Lindgren wrote:

> > You *can* [rewrite guards, etc], even today! (Shameless self-promotion
> follows.)
>
> Well, why not do it, then? :-) In the compiler, you can do it after
> macro preprocessing too, so the case of strange macros that you mention
> subsequently does not occur.

Not sure what you mean here. Of course, the compiler does those things
internally, when translating to Core Erlang: on that level, guards are
plain boolean expressions composed of 'and' and 'or', with 'catch'
blocks inserted as necessary to preserve the Erlang guard semantics. No
confusion there.

It's the source level backwards compatibility that's the problem. The
not entirely unimportant category called "customers" do not want to
change a single thing in their source code unless it is a bug - and
preferably not even recompile anything. They won't switch to a new
version of the compiler if that means new bugs in old code.

There are several small things that I'd personally like to modify in the
Erlang semantics, but it seems like a completely impossible thing to do.
Still, maybe if a "New Erlang" was released, with a compiler that could
be given a backwards-compatibility flag... or is that a pipe dream?

        /Richard

Richard Carlsson (richardc)   (This space intentionally left blank.)
E-mail: Richard.Carlsson WWW: http://www.csd.uu.se/~richardc/
 "Having users is like optimization: the wise course is to delay it."
   -- Paul Graham



Reply | Threaded
Open this post in threaded view
|

Erlang language issues

Thomas Lindgren-3

>Not sure what you mean here. Of course, the compiler does those things
>internally, when translating to Core Erlang: on that level, guards are
>plain boolean expressions composed of 'and' and 'or', with 'catch'
>blocks inserted as necessary to preserve the Erlang guard semantics. No
>confusion there.

There you go then. One could use your tool to automatically rewrite
as much of the code as possible, carefully telling the programmer what
it has done, and also let the Erlang compiler flag obsolete
constructs (much like it does today with some operations) when put in
"migration mode".

>It's the source level backwards compatibility that's the problem. The
>not entirely unimportant category called "customers" do not want to
>change a single thing in their source code unless it is a bug - and
>preferably not even recompile anything. They won't switch to a new
>version of the compiler if that means new bugs in old code.

Since I am working for a company using Erlang, I took the opportunity to
hear how
things are done. Cellpoint is a pretty small firm, with roughly
20 developers and 10 testers. So what does the project management
think about non-backwards compatible changes to Erlang?
I asked our project leader, who supervises the
whole shebang and who has been doing project supervision for a decade
or so.

[I paraphrase and summarize a short conversation here:]
--
In order to move to a new release of Erlang, (1) the system has to be
re-tested; (2) Cellpoint also has made a number of internal patches to
Erlang,
which have to be migrated to a new release. Doing _this_ migration work
is needed regardless of whether Erlang-the-language is backwards compatible
or not, obviously.

There is also (3) installed base. The basic problem is that some customers
are unwilling to migrate to a new product release, but still want you to
patch the code. If so, you have to work with two language versions until
you can persuade customers to switch.

(In Cellpoint's case this is not a big problem; perhaps AXD301 would find
it more painful.)

The actual porting problem is deemed to be minor, as
long as the language changes are minor.
--

So that's one data point (which I hope I summarized fairly). Anybody else
want to comment, please do.

In closing, I'd like to say that I do _not_ propose continuing changes
to the language in this way. Not at all. But I do think we need an orderly
way to change things when the language is getting hairy.

Best,
Thomas



Reply | Threaded
Open this post in threaded view
|

Question on SNMP agent

Daniel Chen Hongwei
In reply to this post by Richard Carlsson-4
Hi,

       We're thinking about the access limitation for certain SNMP agent based on OTP. The <<SNMP User's Guide>> states that we can configure the IP address for the manager ( "IP address for the manager (only this manager will have access to the agent, traps are sent to this one) [dront.ericsson.se]"). Does that means only the configured manager have authority to GET or SET the data from this agent or just means that the agent will only send TRAP to the configured manager?

Thanks a lot!
/Daniel