Discussion:
Small poll
(too old to reply)
unknown
2003-12-10 17:25:44 UTC
Permalink
I was wondering whether the Erlang user community would care
to comment on the following:

In a function like:

test(A) ->
a + 42.

which is either crap (arguably) or a typo (A vs a), how many
Erlang users:

1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it

Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.

Kostis.
unknown
2003-12-10 19:22:59 UTC
Permalink
Hi,
Post by unknown
test(A) ->
a + 42.
For the "A vs a" problem, there is already a warning being issued. Possibly
the full warnings options should be enabled by default?

Since both a and 42 are constants, I feel it would be easy (besides useful)
to be able to detect it at compile time. The operation should even be
evaluated at compile time, IMHO.

regards,
Vlad
unknown
2003-12-10 19:22:49 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
3 please. This is not feature I want anyone here relying on :-)

Sean
unknown
2003-12-10 19:30:44 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
I would like (2). (1) would be a bit weird since there are already
warnings for e.g. unused functions, but (3) might be annoying if I
know test/1 is broken but want to poke around some other functions.

Is this coming from the type-analysis stuff mentioned in the EUC HiPE
presentation? If so, to firmly put myself into broken-record-mode :-),
I think what CMUCL does with the type information it discovers is
pretty nice -- warnings like this and optional "efficiency-notes".

And if you end up doing optional efficiency-notes in HiPE, as in

foo.erl:10: Bit-syntax pattern is not byte-aligned - not native-compiling
foo.erl:20: Can't determine operand types in "X*Y*Z" - not inlining '*'

then I promise to hack up a fancy Emacs front-end like CMUCL has

Loading Image...

.. though I won't suggest we have to make the compiler so
unpredictable as to require efficiency-notes just so that the Emacs
mode can be fancier :-)

(And of course Emacs needs to know a little more than just the line
number to figure out what to underline..)
unknown
2003-12-10 19:33:50 UTC
Permalink
Post by unknown
Post by unknown
test(A) ->
a + 42.
For the "A vs a" problem, there is already a warning being issued. Possibly
the full warnings options should be enabled by default?
Since both a and 42 are constants, I feel it would be easy (besides useful)
to be able to detect it at compile time. The operation should even be
evaluated at compile time, IMHO.
Exactly. And if compile time evaluation fails, give compile time error
{badarith,....}.

best regards,
taavi
unknown
2003-12-10 19:36:06 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
We don't do that, ergo, no preference.
I'd rather see cond added.

:-)
unknown
2003-12-10 20:25:15 UTC
Permalink
On Wed, 10 Dec 2003 18:25:44 +0100 (MET)
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
5. Would prefer it returned the atom 'a42' (PerLang/I! Yesss!!!)

(Dark humour aside... I think #2 makes the most sense.)

-Chris
unknown
2003-12-10 21:02:16 UTC
Permalink
Post by unknown
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.
I would choose 2, which btw is the behaviour you get in this case if you
have set ERL_COMPILER_OPTIONS="[warn_unused_vars]". In the case where you
don't get that warning (for instance by using the variable earlier in the
function) I still like option 2.

It would be easy (and nice) to add a warning at all points where constant
propagation detects a possible (probable?) run-time error. I use the word
possible since I would like the warning in the case

test(A,B) ->
case B of
true -> a + 42;
_ -> A.

The above function is also a good example of why option 3 is bad. There
might actually be a reason that the programmer wants to generate a
run-time error in the case that B is true (I can't think of one, but...).
Anyway it would be bad to disallow program lines that might actually never
even be reached.


btw Since I am delurking I should probably say hi to the list. Hi list.

I am a last year student at Uppsala University, studying Computer Science
and Mathematics (in no particular order of preference). I recently took a
course in compiler techniques where we rewrote the constant propagation
pass on the ICode level for HiPE. This made me interested in Erlang so I
started lurking here.

My email log says that I have been lurking since July, but in reality I
have been deleting more than I have read; increasing my read/delete ratio
steadily. Up to almost 1 the last week (or "division by zero exception"
depending on how you count).

#Luna
--
Daniel Luna | Top reasons that I have a beard:
luna | a) Laziness.
http://www.update.uu.se/~luna/ | b) I can.
Don't look at my homepage (it stinks).| c) I can get away with it.
unknown
2003-12-10 21:31:38 UTC
Permalink
Post by unknown
Post by unknown
I was wondering whether the Erlang user community
would care
Post by unknown
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs
a), how many
Post by unknown
1. Are content with the current situation where
the compiler
Post by unknown
happily compiles this program
2. Would like to see a warning, but a .beam file
generated
Post by unknown
nevetheless
3. Would prefer if the compiler in R10 refused to
compile it
I prefer (2) with an OPTIONAL warning, and otherwise
concur with Luke. CMUCL:s user interaction is the most
sophisticated I know of, so building on efficiency
notes etc. sounds like a good idea. (As a principle,
it is a bad idea to leave the programmer guessing
about what sort of tweaks should be made for good
performance.)

However, there are some problems. First, it should be
noted that Common Lisp has a much richer type system
than Erlang. Expressing that something is a fixnum or
a specialized sort of tuple is tedious in Erlang.

Second, the various type annotations one might want to
make are probably easier and more elegantly done with
macros in CL than in Erlang. Some easy way of
communicating those to the compiler is probably
needed.

In short, the compiler-user interaction is worth some
careful thinking.

Best,
Thomas

PS. As regards warnings in general, I would like two
more options:

- Warn when a variable occurrence is matched rather
than bound. This has turned out to be a bug more often
than I would have liked.

- Warn about unknown compiler options (currently just ignored)

__________________________________
Do you Yahoo!?
New Yahoo! Photos - easier uploading and sharing.
http://photos.yahoo.com/
unknown
2003-12-10 21:44:39 UTC
Permalink
Post by unknown
It would be easy (and nice) to add a warning at all points where constant
propagation detects a possible (probable?) run-time error. I use the word
possible since I would like the warning in the case
test(A,B) ->
case B of
true -> a + 42;
_ -> A.
The above function is also a good example of why option 3 is bad. There
might actually be a reason that the programmer wants to generate a
run-time error in the case that B is true (I can't think of one, but...).
Anyway it would be bad to disallow program lines that might actually never
even be reached.
Still disagree. Erlang specification does not disallow optimizing
compilers, which try to evaluate constant expressions compile time.

Language allready has special construct for throwing exceptions -
throw(...).

Why not force explicit usage of throw() instead of obscure evaluation
side effects?


best regards,
taavi
unknown
2003-12-11 03:39:00 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
I liked the "perlang" suggestion ;-).

Seriously, my preference (in order) would be: #2, #3, #1. Or maybe #3
by default, but a compiler option to use #2.

I see three uses for writing such code:

1. As a programmer, one wants to know what exceptions will be generated
by certain error conditions, and/or how your program will react to
them. So one writes a little code that artificially generates such
errors. Such code is never released to the world.

2. As a tester, one may want to do much the same thing. I'm not really
sure that this case is really distinct from the first one, but in my
mind it is a different part of the process. Also, testers tend to be
rather less well versed in the subtleties of compiler behaviour, and
thus less capable of doing tricky things to "fool" the compiler.

3. One wants to generate a particular error in precisely the "standard"
format, regardless of how the internals of the erlang system may change
in future releases.

I believe that case #3 is so rare that it is unimportant (or perhaps it
is even non-existent). But while they are not something that I would
raise a fuss about, I'm not sure that the first two cases should be
ignored.

Just the other day, I was doing weird things to see what kind of NaNs
libm/pentium produce at runtime. I think I fooled gcc into actually
performing the nasty operations at run time instead of compile time (and
maybe it generates the same result even when it optimises the operations
away), but I may have made a mistake. It's nicer when one doesn't have
to play games with the compiler.

(BTW, the semantics of floating point in erlang seem to be rather messed
up. Is their any intention to clean it up some time? I usually just
try to pretend that erlang doesn't have floats).
Post by unknown
Still disagree. Erlang specification does not disallow optimizing
compilers, which try to evaluate constant expressions compile time.
Language allready has special construct for throwing exceptions -
throw(...).
Why not force explicit usage of throw() instead of obscure evaluation
side effects?
I agree that an explicit exit() is almost always better. But isn't
exit() what you want, not throw()? If so, this demonstrates the point
that people aren't really so aware of the correct way to simulate
errors. So it might be better (occasionally) if they can just write
straightforward code to generate a desired error condition.

/Lon
unknown
2003-12-11 06:11:23 UTC
Permalink
Lon, you saved me a lot of typing. I agree with all of what you wrote.

/Uffe

On Thu, 11 Dec 2003 04:39:00 +0100, Lon Willett
Post by unknown
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
I liked the "perlang" suggestion ;-).
Seriously, my preference (in order) would be: #2, #3, #1. Or maybe #3
by default, but a compiler option to use #2.
1. As a programmer, one wants to know what exceptions will be generated
by certain error conditions, and/or how your program will react to
them. So one writes a little code that artificially generates such
errors. Such code is never released to the world.
2. As a tester, one may want to do much the same thing. I'm not really
sure that this case is really distinct from the first one, but in my
mind it is a different part of the process. Also, testers tend to be
rather less well versed in the subtleties of compiler behaviour, and
thus less capable of doing tricky things to "fool" the compiler.
3. One wants to generate a particular error in precisely the "standard"
format, regardless of how the internals of the erlang system may change
in future releases.
I believe that case #3 is so rare that it is unimportant (or perhaps it
is even non-existent). But while they are not something that I would
raise a fuss about, I'm not sure that the first two cases should be
ignored.
Just the other day, I was doing weird things to see what kind of NaNs
libm/pentium produce at runtime. I think I fooled gcc into actually
performing the nasty operations at run time instead of compile time (and
maybe it generates the same result even when it optimises the operations
away), but I may have made a mistake. It's nicer when one doesn't have
to play games with the compiler.
(BTW, the semantics of floating point in erlang seem to be rather messed
up. Is their any intention to clean it up some time? I usually just
try to pretend that erlang doesn't have floats).
Post by unknown
Still disagree. Erlang specification does not disallow optimizing
compilers, which try to evaluate constant expressions compile time.
Language allready has special construct for throwing exceptions -
throw(...).
Why not force explicit usage of throw() instead of obscure evaluation
side effects?
I agree that an explicit exit() is almost always better. But isn't
exit() what you want, not throw()? If so, this demonstrates the point
that people aren't really so aware of the correct way to simulate
errors. So it might be better (occasionally) if they can just write
straightforward code to generate a desired error condition.
/Lon
--
Ulf Wiger
unknown
2003-12-11 07:47:09 UTC
Permalink
Hi,

From: "Lon Willett" <p0rj2el602>
Post by unknown
1. As a programmer, one wants to know what exceptions will be generated
by certain error conditions, and/or how your program will react to
them. So one writes a little code that artificially generates such
errors. Such code is never released to the world.
In such a case, I'd rather have a default configuration that doesn't support
such things (for production code) and a compiler option that makes it accept
such erroneous code.

regards,
Vlad
unknown
2003-12-11 08:11:03 UTC
Permalink
I am certainly putting my money on no 1!

The case when "a + 32" craches my code will be easily detected
the first time I run that piece of code, and since I *do* test my
code before delivery, this is a non-problem. I would not like
having anyone wasting their valuable time improving the compiler
to catch this and also wasting more CPU power on everyones
computer when compiling.

/Peter
Post by unknown
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.
Kostis.
unknown
2003-12-11 08:42:47 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
i choose 3.


bengt
unknown
2003-12-11 09:09:44 UTC
Permalink
Well ...

a + 42

IS legal code, it might be part of a test suit to test that the
run-time exception handling stuff works.

We need pragmas :-)

a + 42 generates a hard compiler error (not like to today)
and refuses to compile ie your (3)

PRAGMA deliberate_error
a + 42
END

works like (1)

This is what I called "intentionality" in my thesis :-) - the thing is
you don't know why the programmer wrote 42+a - there are two cases

a) The programmer knew it was an error, or,
b) It was an error

Adding a pragma allows the best of both worlds, :-)

/DrJoe :-)
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.
Kostis.
unknown
2003-12-11 10:46:32 UTC
Permalink
Post by unknown
/DrJoe :-)
Congratulations!!

It was certainly a PhD that was overdue for some time now... :)

/Peter
unknown
2003-12-11 09:20:39 UTC
Permalink
Post by unknown
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Hello,

I am quite content with the current situation. I always compile
with [warn_unused_vars], and even if A were used elsewhere I would
detect the runtime error in my unit tests.

Please don't make compile times any longer!

Regards,

Dominic Williams.
unknown
2003-12-11 09:25:19 UTC
Permalink
We in the Erlang/OTP group at Ericsson have also discussed
this question recently.

For the R10B release, we will probably change the compiler
according to #2 or #3. It is not hard that hard to do. What
we will do is to keep line number information a bit longer
so that the optimization passes that can easily detect this
kind of type error also has access to the original line number
when writing the message.

In the Erlang/OTP group, there are different opinions whether
#2 or #3 is the best way to go.

Personally, I think that #2 is the way to go, but most others
in the Erlang/OTP group seem to favor #3.

/Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.
Kostis.
unknown
2003-12-11 09:33:47 UTC
Permalink
Post by unknown
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
I wanted to answer "3, of course" until I realized the
follow-up poll questions might be embarrassing.


- Suppose we reject a+42. Then what about:
test(A) -> a<42.
This is exactly the same typo. Can we also detect it and still
allow < between arbitrary terms (e.g. keys in data structures) ?

The ML way would be to declare < with the type:
'<'(T,T) -> bool().
and allow the programmer to explicitly widen the type of
operands to term() when needed. But this does not make sense
without a clear notion of type inference (i.e. what is the type
of 'a' ? atom() ? The atom 'a' ? a|42 ?).


- More generally, the verification will not be complete.
How do we explain the rationale when a programmer complains
"Hey, the compiler caught this mistake, but not that one" ?


- Should the compiler also reject:
test(A) ->
if a+42 == 0 -> true;
a+42 =/= 0 -> false;
true -> fish
end.
In other words, do people occasionally rely on the fact that
exceptions are silently ignored in guards, possibly in more
elaborate ways than above ?


All things considered, obviously even a local, partial
typechecker would be welcome.

And since Christmas is approaching, #1 on my wishlist would be
a tool which unifies in a consistent way:
- a syntax for function and data type declarations
- erldoc
- typechecking
- uniqatom
- xref
- user-definable, typed behaviours, as in:
%% File: gen_server.bhv
+defbehaviour gen_server(InitArg, State, Request, Result).
+type handle_call(Request, {pid(),term()}, State) ->
{reply, Result, State}
| {reply, Result, State, timeout_val()}
| {noreply, State}
| {noreply, State, timeout_val()}
| {stop, Reason, Result, State}
| {stop, Reason, State}.
- warnings related to the emulation of destructive updates with
single assignment, as in:
handle_call({set,NewValue}, From, State) ->
State1 = State#state{val=NewValue},
State2 = State1#state{timestamp=now()},
{reply, ok, State1}. % Catch this.

-- Pascal
unknown
2003-12-11 09:35:29 UTC
Permalink
Hi Joe,
Post by unknown
We need pragmas :-)
And macros, and conditional compilation, and packages...
Pity, I was enjoying Erlang, now I might as well go back to C++.
Post by unknown
This is what I called "intentionality" in my thesis :-)
How did your "defense" go?
Post by unknown
/DrJoe :-)
Well, apparently! Congratulations, Dr!

Regards,

Dominic Williams.
unknown
2003-12-11 09:41:40 UTC
Permalink
You must be kidding, right?

As it is now the compiler always does constant propagation and folding.
If you write:
A = 1 + 2,
you get
A = 3,
in the compiled beam code.
(These types of expressions do come up, especially if you are using
macros...)

The problem is that if you write
a + 1,
then the compiler has to spend extra time compiling the expression, handling
the
special case that constant folding does not work, and produce larger
code than necessary.
I would not like having anyone wasting their valuable time
improving the compiler to catch this
This already has to be cached and handled by the compiler.
and also wasting more
CPU power on everyones computer when compiling.
More CPU power is probably used today in order to 'fix' this
special case, at least for native code.

Erik
-----Original Message-----
From: owner-erlang-questions
[mailto:owner-erlang-questions] On Behalf Of Peter Lund
Sent: Thursday,11 December, 2003 09:11
To: kostis
Cc: erlang-questions
Subject: Re: Small poll
I am certainly putting my money on no 1!
The case when "a + 32" craches my code will be easily
detected the first time I run that piece of code, and since I
*do* test my code before delivery, this is a non-problem. I
would not like having anyone wasting their valuable time
improving the compiler to catch this and also wasting more
CPU power on everyones computer when compiling.
/Peter
which is either crap (arguably) or a typo (A vs a), how many Erlang
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static type
checking, but for really "basic" checks.
Kostis.
unknown
2003-12-11 10:19:55 UTC
Permalink
If the effort (in sence of valuable time) already has been made,
then it is obviously a good thing to have it doing 2 by default
and 3 by adding some compiler directive.
Post by unknown
You must be kidding, right?
As it is now the compiler always does constant propagation and folding.
A = 1 + 2,
you get
A = 3,
in the compiled beam code.
(These types of expressions do come up, especially if you are using
macros...)
The problem is that if you write
a + 1,
then the compiler has to spend extra time compiling the expression,
handling the
special case that constant folding does not work, and produce larger
code than necessary.
I would not like having anyone wasting their valuable time
improving the compiler to catch this
This already has to be cached and handled by the compiler.
and also wasting more
CPU power on everyones computer when compiling.
More CPU power is probably used today in order to 'fix' this
special case, at least for native code.
Erik
unknown
2003-12-11 10:17:52 UTC
Permalink
Erm, a side question: since the question comes from the Hipe group, I feel
compelled to check if it refers to the compiler in general or just the Hipe
variant?

regards,
Vlad
unknown
2003-12-11 09:30:46 UTC
Permalink
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
I vote for #2 (followed by #3 + flag to force compilation, followed
by #1, followed by plain #3).


/martin
unknown
2003-12-11 01:37:42 UTC
Permalink
Glad to receive so many responses. Let me reply on a few of them.
Post by unknown
Post by unknown
Post by unknown
test(A) ->
a + 42.
For the "A vs a" problem, there is already a warning being issued. Possibly
the full warnings options should be enabled by default?
First of all, let's disconnect this thread from "unused_variables"
(although the warning **SHOULD** be turned on by default). Let's
say that the program was:

test(A) ->
bar(A),
a + 42.
Post by unknown
Post by unknown
Since both a and 42 are constants, I feel it would be easy (besides useful)
to be able to detect it at compile time. The operation should even be
evaluated at compile time, IMHO.
Exactly. And if compile time evaluation fails, give compile time error
{badarith,....}.
Regardless of whether this is evaluated at compile time or not, there
are still three options as I wrote in my previous mail:

1. the compiler happily compiles this program, but simply replaces
a + 42 with a call to spit {badarith,....}
2. generate a warning, but also byte code.
3. the compiler refuses to compile such erroneous code.

I will try to make a case for 3 below.

Option 1, seems strange to me: The compiler has found out that a particular
evaluation will result in an error, but does not inform its user about it
and moreover generates more code (in terms of space) than that needed for
the expression "a + 42" (if the expression is statically evaluated to
badarith) or refuses to do work at compile time, leaving this work for
run-time (which is what the BEAM compiler currently does).

Option 2 on the surface seems a reasonable choice, but is it really ?
I claim that at least 90% of all such cases are programming errors.
As Taavi mentioned, if the programmer _really_ wants to generate an
error, there is throw() and the a + 42 can be its argument (either
as a string or as a tuple). Seems much, much cleaner to me.

Now let me tell you why I am interested in 3:

In the context of generating native code, it is really a nuisance
to have to handle all sorts of illegal combinations of arguments
to "basic" primitives like "+". Moreover, in the case of HiPE,
because the compiler is (much) more sophisticated than the BEAM
one, it actually discovers quite a lot more cases of such errors.
Currently, we try our best to "immitate" the behaviour of the BEAM
compiler, but in sleepless nights like this one, I keep wondering
whether there should really be a limit to the level of naivety
one pretends to show, or shouldn't there be?

(Just in case it is not obvious, this is a more general issue,
not just related to the simple "a+42" case I chose to illustrate
my point.)

Cheers,
Kostis.
Post by unknown
But we presently rely on this behavior and do not
treat it as a warning.
Exactly how do you "rely" on this?
unknown
2003-12-11 23:00:07 UTC
Permalink
Post by unknown
In the context of generating native code, it is really a nuisance
to have to handle all sorts of illegal combinations of arguments
to "basic" primitives like "+". Moreover, in the case of HiPE,
because the compiler is (much) more sophisticated than the BEAM
one, it actually discovers quite a lot more cases of such errors.
Currently, we try our best to "immitate" the behaviour of the BEAM
compiler, but in sleepless nights like this one, I keep wondering
whether there should really be a limit to the level of naivety
one pretends to show, or shouldn't there be?
Another option would be "2.5". Suppose the function is:

foo(A) ->
X = a+42,
bar(X).

having detected the problem you could compile it as essentially:

foo(A) ->
throw({badarith, a, 42}).

Would that ease the compiler work any?

Some other compilers do exactly this. I know because once, due to a
type-inferencer bug, I got a message like:

Error: 42 is not an integer.

because the compiler had wrongly convinced itself that a variable
could never be bound to an integer, but at runtime when the error was
generated it actually was :-)
unknown
2003-12-12 01:48:25 UTC
Permalink
Post by unknown
1. the compiler happily compiles this program, but simply replaces
a + 42 with a call to spit {badarith,....}
(Oops, I see you were way ahead of me already. I was too eager to
share that '42 not integer' anecdote :-))
unknown
2003-12-12 10:08:21 UTC
Permalink
Kostis Sagonas <kostis> writes:

[...]
Post by unknown
In the context of generating native code, it is really a nuisance
to have to handle all sorts of illegal combinations of arguments
to "basic" primitives like "+". Moreover, in the case of HiPE,
because the compiler is (much) more sophisticated than the BEAM
one, it actually discovers quite a lot more cases of such errors.
Currently, we try our best to "immitate" the behaviour of the BEAM
compiler, but in sleepless nights like this one, I keep wondering
whether there should really be a limit to the level of naivety
one pretends to show, or shouldn't there be?
We in the OTP team are planning for the R10B release to generate more
warnings for suspect code. We will do that in one of the Core Erlang
passes (which is common pass used for both threaded Beam code and native
HiPE code). At the same time as the warning is generated, the offending
code will be replaced with an explicit failure (e.g. erlang:fault/2), that
should be no problem for the later HiPE passes to handle.

Thus, the work only needs to be done once.

As I have already written in a previous answer, we have different opinions
within the Erlang/OTP team at Ericsson whether #2 or #3 is the best solution,
but as someone pointed out, most of work is in detecting the suspect code
anyway. Either #2 or #3 will be implemented in the common parts of the
compiler in R10B.

/Bjorn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2003-12-11 10:47:02 UTC
Permalink
I've sent the following at about 3:00 last night, but have not seen
it in the list yet. I am re-sending it again; apologies if you get
it twice.

Kostis.

---------------------------------------------------------------------

Glad to receive so many responses. Let me reply on a few of them.
Post by unknown
Post by unknown
Post by unknown
test(A) ->
a + 42.
For the "A vs a" problem, there is already a warning being issued. Possibly
the full warnings options should be enabled by default?
First of all, let's disconnect this thread from "unused_variables"
(although the warning **SHOULD** be turned on by default). Let's
say that the program was:

test(A) ->
bar(A),
a + 42.
Post by unknown
Post by unknown
Since both a and 42 are constants, I feel it would be easy (besides useful)
to be able to detect it at compile time. The operation should even be
evaluated at compile time, IMHO.
Exactly. And if compile time evaluation fails, give compile time error
{badarith,....}.
Regardless of whether this is evaluated at compile time or not, there
are still three options as I wrote in my previous mail:

1. the compiler happily compiles this program, but simply replaces
a + 42 with a call to spit {badarith,....}
2. generate a warning, but also byte code.
3. the compiler refuses to compile such erroneous code.

I will try to make a case for 3 below.

Option 1, seems strange to me: The compiler has found out that a particular
evaluation will result in an error, but does not inform its user about it
and moreover generates more code (in terms of space) than that needed for
the expression "a + 42" (if the expression is statically evaluated to
badarith) or refuses to do work at compile time, leaving this work for
run-time (which is what the BEAM compiler currently does).

Option 2 on the surface seems a reasonable choice, but is it really ?
I claim that at least 90% of all such cases are programming errors.
As Taavi mentioned, if the programmer _really_ wants to generate an
error, there is throw() and the a + 42 can be its argument (either
as a string or as a tuple). Seems much, much cleaner to me.

Now let me tell you why I am interested in 3:

In the context of generating native code, it is really a nuisance
to have to handle all sorts of illegal combinations of arguments
to "basic" primitives like "+". Moreover, in the case of HiPE,
because the compiler is (much) more sophisticated than the BEAM
one, it actually discovers quite a lot more cases of such errors.
Currently, we try our best to "immitate" the behaviour of the BEAM
compiler, but in sleepless nights like this one, I keep wondering
whether there should really be a limit to the level of naivety
one pretends to show, or shouldn't there be?

(Just in case it is not obvious, this is a more general issue,
not just related to the simple "a+42" case I chose to illustrate
my point.)

Cheers,
Kostis.
Post by unknown
But we presently rely on this behavior and do not
treat it as a warning.
Exactly how do you "rely" on this?
unknown
2003-12-11 22:56:58 UTC
Permalink
Post by unknown
I've sent the following at about 3:00 last night, but have not seen
it in the list yet. I am re-sending it again; apologies if you get
it twice.
The delay is because you are not a member of the list using the
address <kostis>. I have to manually approve your
postings each time (and today I had no access to my mail). You joined
the list as <kostis>,

Do you want to change address or use both?

kent
unknown
2003-12-11 23:10:34 UTC
Permalink
Post by unknown
Post by unknown
But we presently rely on this behavior and do not
treat it as a warning.
Exactly how do you "rely" on this?
Wonder not. Sorry; I misinterpreted your query. I thought this was
an "undefined variable" issue. Since it is not I hereby redact my
statement.
On the other hand I am glad to hear that HIPE does more checking. The
Engineer in me wants as many constraints as are possible to enforce so
I can avoid writing a number of runtime tests. Yes I love Erlang.
But I sure do miss the days of defining my own rules in C++ and
letting the compiler check them for me. Please keep up the good work
with the HIPE compiler.
Sincerely,
Eric
unknown
2003-12-11 23:30:09 UTC
Permalink
Concerning

test(A) -> a + 42. /* 1 */

I like to have tools that I can form mental models of.
Suppose we have a compiler that catches the clause above.
Now let's change it:

test(A) -> X = a, X + 42. /* 2 */

Now /* 2 */ _ought_ to have exactly the same semantics as /* 1 */.
But will it? Not if the compiler lets /* 2 */ past but rejects /* 1 */.
To be consistent, the compiler had better do some kind of data flow
analysis. But then we bring in

test(A) -> f(A) + 42. /* 3 */
f(A) -> a.

Apart from tracing, run time, and number of reductions, we'd expect
/* 3 */ to behave just like /* 1 */. Is it proposed that the compiler
will catch this also? Now we're into inter-procedural data flow analysis.

In order to predict which clauses will be rejected and which will be
allowed through, suddenly I have to know more than syntax rules and
variable scope, I have to know quite a bit about how the compiler works.

Now let's try another one. This one is NOT expected to have the same
semantics as /* 1 */, /* 2 */, or /* 3 */.

test(A) -> if 0==0 -> A+42 ; 1==0 -> a+42 end. /* 4 */

Why should the compiler warn about a+42 when there is no possibility of
that expression ever being evaluated?

In order to figure out whether /* 4 */ will be accepted or rejected,
I have to know whether the compiler does its weak type checking before
or after dead code elimination.

A compiler can warn about anything. There's nothing to stop a compiler
saying "Warning: your PC has a camera, you are UGLY." or even
"Warning: code compiled on Friday 13 may not work" or
"Warning: I've seen this before and I didn't like it then either".
But *inconsistent* rejection is another matter entirely.

Consider the following Haskell program as an analogue:

module Main(main)
where
main = print (x `div` x)
x :: Int
x = 0

This is guaranteed to produce an error at run time. Guaranteed.
0 is not a legal right argument for `div`; this is just like the
fact that 'a' is not a legal argument for + .

I tried this program with three different Haskell compilers.
All of them were completely silent about it at compile time.
All of them gave a run time exception.
Yet some of them do quite serious unfolding; at least one of them
_must_ have noticed that I was asking for (0 `div` 0).

My vote would therefore be for option 2:
warn about it but still generate code.
unknown
2003-12-12 02:01:16 UTC
Permalink
Kostis Sagonas <kostis> wrote:
Option 2 on the surface seems a reasonable choice, but is it really ?
I claim that at least 90% of all such cases are programming errors.

And a warning from the compiler tells the programmer about it.

In the context of generating native code, it is really a nuisance
to have to handle all sorts of illegal combinations of arguments
to "basic" primitives like "+".

But choosing option 3 does not remove this requirement.

-module(foo).
-export(bar/1).
bar(X) -> X + 42.

a+42 is *not* the typical case. This is. And it is precisely in this
case that you have no idea what X is going to be.

The following cases are surely legal:
fixnum + fixnum -- add, may overflow and generate bignum
fixnum + bignum -- call out-of-line code
fixnum + flonum -- convert, add, box (lazily)
bignum + fixnum -- call out-of-line code
bignum + bignum -- call out-of-line code
bignum + flonum -- call out-of-line code
flonum + fixnum -- convert, add, box (lazily)
flonum + bignum -- call out-of-line code
flonum + flonum -- add, box (lazily)

By box (lazily) I mean that the compiler may leave the result in a
floating-point register and only box it when/if it is returned or
included in a data structure.

With 9 legal cases, surely handling one more ("none of the above") is
no big deal. There would appear to be the following interesting cases:
(1) The compiler can prove that X and Y are fixnums:
addcc X, Y, result
bpvs,pn %icc,overflow_handler
nop
(2) The compiler can prove that X is a fixnum and Y a flonum:
(get X into an fp register as 32-bit integer; get Y into an fp reg.)
fitod fX, fX
fadd fX, fY, fresult
(3) The compiler can prove that X is a flonum and Y a fixnum:
(get Y into fp reg as 32-bit integer; get X into fp reg.)
fitod fY, fY
fadd fX, fY, fresult
(4) The compiler can prove that X and Y are both flonums
fadd fX, fY, fresult
(5) None of the above
taddcc X, Y, result
bpvc,pt %icc,1f
nop
call general_case
nop
1:
Or, on a machine without tagged add,
or X, Y, result
andcc result, 3, %g0
bpnz,pn 1f
add X, Y, result
bpvc,pt %icc,2f
nop
1: call general_case
nop
2:

This can be simplified a bit. Suppose Y, for example, is a constant
which will bit as an immediate operand. Then

andcc X, 3, %g0
bpnz,pn 1f
add X, immedY, result
bpvc,pt %icc,2f
nop
1: call general_case
set immedY, Y
2:

But it really can't be simplified _much_.

The problem with (1), of course, is that just because the operands
of + are both fixnums doesn't mean the result is; the result could be
a fixnum or a bignum, so when you do X+X+X the second add has to be
the general case (simplified because we don't need the 'or'; we know
X is still fixnum, but we _don't_ know that X+X is).

There are only three ways I can think of to get an integer calculation
down to a single instruction:

(1) Use taddcctv, which traps on overflow, and put the general case in
the trap handler. However, that does nasty things to an Ultra's
pipeline and is now a deprecated instruction. Keeps the code short,
though. This only works for add, subtract, and compare, of course,
and isn't available for most machines.

(2) Use some really amazing interval analysis as well as type inference.
Erlang doesn't give you much to get started with, though. About the
only thing I can think of is that starting with size(_) and counting
down towards 0 should be safe.

(3) Stop supporting Erlang, and implement a related language in which
only fixnums exist, no bignums. (There was some discussion at one
time about having the Erlang standard only require 64-bit integers,
but even those would count as bignums on a 32-bit machine.) Of
course this would cause great problems interoperating with C, Java,
CORBA, and so on (C99, Java, and CORBA all require 64-bit integers,
don't they?), so that won't do.

Moreover, in the case of HiPE,
because the compiler is (much) more sophisticated than the BEAM
one, it actually discovers quite a lot more cases of such errors.

That's great. So issue a lot more warnings. The BEAM and HiPE compilers
should accept *exactly* the same language. (Although HiPE could "accept"
some files in the sense of giving up and leaving them as BEAM.)
But "accept" is not the same as "not warn about".

I keep wondering
whether there should really be a limit to the level of naivety
one pretends to show, or shouldn't there be?

This is an argument against option 1. It is NOT an argument for option 3.
Option 2 (complain loudly but accept, compatibly with BEAM, and generate
code which generates compatible run-time behaviour) is also non-naive.
unknown
2003-12-12 18:01:19 UTC
Permalink
Post by unknown
There are only three ways I can think of to get an integer calculation
[...]
Post by unknown
(2) Use some really amazing interval analysis as well as type inference.
Erlang doesn't give you much to get started with, though. About the
only thing I can think of is that starting with size(_) and counting
down towards 0 should be safe.
Related to the "Network benchmark" thread: one place in Erlang that
you get precise integer-size type information is with the bit syntax,
e.g. "<<X:16, Y:16>> = Bin, X + Y" adds two 16-bit fixnums. Is that a
single-instruction case?

It's possible to imagine a heavy duty optimizer generating fast code
for e.g. checksumming an IP packet, doing lots of 16-bit arithmetic on
values coming out of a binary.

No idea if that's practical, but maybe something to drool over in idle
hours ;-)

By the way, at EUC Thomas Lindgren mentioned the bother of taking care
of "bump reductions" in loops, and Tony Rogvall said something to the
effect of "or we could get rid of bump_reductions and do it properly
instead." What would be the "proper" way?

Cheers,
Luke
unknown
2003-12-16 08:08:18 UTC
Permalink
I have read through the discussion on this suggestion and found it a little strange, to say the least.

There are a few points I would like to make:

1. If I understood Richard correctly he means that the compiler should be careful when it can't do things consistently, in this case warn about a a construction which will generate an error. He also gave some equivalent code to "a+42" which the compiler would not warn about. His first case, "X = a, X+42" is especially funny as the compiler will generate exactly the same code for it, as it has some simple constant propagation in it. I completely agree with Richard.

2. As Dr. Joe (again congratulations Joe) points out "a+42" IS LEGAL ERLANG! Sorry for shouting but the point must be stressed. How can any one seriously suggest that the compiler disallow legal code? You are actually changing the semantics of the language when you do this. Are you aware of this fact?

3. What is the point is the compiler transforming it to exit({badarith,...})? What are you exactly gaining by doing it? Not saving code anyway, and hardly runtime either.

So my suggestion would be #2, generate a warning. I say that the compiler should disallow illegal code and allow all legal code while warning for potentially stupid things. It should NEVER disallow legal code. Sorry Bjorn but this also includes a compiler option which transforms all warnings to errors. Do you seriously mean that it should be illegal to have singleton variables, unused functions, bad calls to format etc? It is one thing to warn, but to disallow?

The compiler should help me and not hinder me by trying to be too stupidly smart. As example just look at what Word/Outlook Express do for some really terrifying examples. This is somewhat similar to the syntax change suggestion a while back try and catch the case where there was a missing blank in X =<<...>>.

Sorry for getting worked up about this but I wish people would think through their suggestions a bit more and consider the deeper implications of them.

Finally one definite reason not to outlaw a+42 is that it is one of my standard ways of generating an error in code, being much shorter to write than exit(some_bogus_exit_value). I also use 1=2 (N.B. only one =). Which should also be banned I suppose.

Robert

P.S. If things like this are added to the compiler then it is time for an alternate compiler.

----- Original Message -----
From: "Kostis Sagonas" <kostis>
To: <erlang-questions>
Sent: Wednesday, December 10, 2003 6:25 PM
Subject: Small poll
Post by unknown
I was wondering whether the Erlang user community would care
test(A) ->
a + 42.
which is either crap (arguably) or a typo (A vs a), how many
1. Are content with the current situation where the compiler
happily compiles this program
2. Would like to see a warning, but a .beam file generated
nevetheless
3. Would prefer if the compiler in R10 refused to compile it
Notice I am not talking about any serious attempt to static
type checking, but for really "basic" checks.
Kostis.
unknown
2003-12-16 08:45:59 UTC
Permalink
Post by unknown
Finally one definite reason not to outlaw a+42 is that it is one of my
standard ways of generating an error in code, being much shorter to
write than exit(some_bogus_exit_value). I also use 1=2 (N.B. only one
=).
I've often wondered how many different ways people do this. I do 1/0,
Klacke does a=b, you do 1=2., ..... :-)
unknown
2003-12-16 14:00:11 UTC
Permalink
Post by unknown
I've often wondered how many different ways people do this. I do 1/0,
Klacke does a=b, you do 1=2., ..... :-)
Ahh, now you are getting somwhere, let's discuss which is the fastest :-) :-)
--
-- Goran
------------------------- May the Snow be with you ----
Goran Bage, MobileArts, www.mobilearts.se
Tj?rhovsgatan 56, SE-116 28 Stockholm, Sweden
email:goran.bage, phone: +46 733 358405
unknown
2003-12-16 09:32:39 UTC
Permalink
Robert Virding wrote:
...deleted
Post by unknown
2. As Dr. Joe (again congratulations Joe) points out "a+42" IS LEGAL ERLANG! Sorry for shouting but the point must be stressed. How can any one seriously suggest that the compiler disallow legal code? You are actually changing the semantics of the language when you do this. Are you aware of this fact?
i did not know ''a+42'' was legal. i thought it generated an error.


...deleted
Post by unknown
Finally one definite reason not to outlaw a+42 is that it is one of my standard ways of generating an error in code, being much shorter to write than exit(some_bogus_exit_value).
i think that a+42, with a comment stating that this is indeed intended
to cause an error, and an explanation why you want an error here, is
probably longer than erlang:exit(why_you_want_an_error).

presumably you do not suggest putting a+42, without any explanation,
into the source?
Post by unknown
P.S. If things like this are added to the compiler then it is time for an alternate compiler.
i think this is a very good suggestion. if it frightens anybody i would
like to mention that it was egcs that became gcc-2.


bengt
unknown
2003-12-17 00:02:57 UTC
Permalink
From: "Bengt Kleberg" <Bengt.Kleberg>
Post by unknown
...deleted
Post by unknown
2. As Dr. Joe (again congratulations Joe) points out "a+42" IS LEGAL ERLANG! Sorry for shouting but the point must be stressed. How can any one seriously suggest that the compiler disallow legal code? You are actually changing the semantics of the language when you do this. Are you aware of this fact?
i did not know ''a+42'' was legal. i thought it generated an error.
It is a perfectly legal expression but when evaluated it generates an error. As errors are a part of Erlangs semantics you can't go and just eliminate any expression which might generate one. If you do that then you are changing the semantics of the language. Also doing it in an ad hoc way in the compiler by catching expressions which just happen to be easy to detect would be very confusing. I mean why flag a+42 and not A=a,A+42 which is also very simple and just as likely to cause an error. A compiler should disallow illegal code, do its best with legal code and try to be helpful by warning about legal, but potentially confusing or with misleading results, code or constructions.

That is why the compiler's warnings should stay as warnings and never become errors.
Post by unknown
presumably you do not suggest putting a+42, without any explanation,
into the source?
Well, it's pretty obvious that if you write something like a+42 in your code then you're intending it to fail. I mean there are limits. :-) I usually document code which is not plainly obvious and which I feel I might have difficulty remembering what it did at a later date, or does something tricky and smart. This and a comment at the beginning of the function usually suffices. I am of the opinion that having to many comments tends to make everything difficult to read. Also having a comment standard helps to keep comments short. Read through the compiler and parts of the libraries (the more basic ones) to see how I code.

Robert
unknown
2003-12-17 00:17:02 UTC
Permalink
Post by unknown
That is why the compiler's warnings should stay as warnings and never become errors.
To properly enforce this, perhaps we should also suppress the warnings?

#!/usr/bin/awk -f
BEGIN { status = 0 }
{ print $0 }
/Warning:/ { status = 1 }
END { exit(status) }

;-)
unknown
2003-12-17 09:32:18 UTC
Permalink
Robert Virding wrote:
...deleted
Post by unknown
It is a perfectly legal expression but when evaluated it generates an error. As errors are a part of Erlangs semantics you can't go and just eliminate any expression which might generate one.
may i continue to respectfully belive that code that will _always_
generate an error should stop the compilation?


...deleted
Post by unknown
A compiler should disallow illegal code, do its best with legal code
and try to be helpful by warning about legal, but potentially confusing
or with misleading results, code or constructions.

this is something i can fully agree with. i just happen to think that
''do its best'' in the case of only ''a+42'' is to not compile it.


...deleted
Post by unknown
Well, it's pretty obvious that if you write something like a+42 in your code then you're intending it to fail. I mean there are limits. :-) I usually document code which is not plainly obvious and which I feel I might have difficulty remembering what it did at a later date, or does something tricky and smart. This and a comment at the beginning of the function usually suffices. I am of the opinion that having to many comments tends to make everything difficult to read. Also having a comment standard helps to keep comments short. Read through the compiler and parts of the libraries (the more basic ones) to see how I code.
you are much to kind to me. if i write ''a+42'' it is because i am
making a stupid mistake, nothing else. probably i intended it to be
''A+42''.

your comment standard seems very good. i hope that i am using it (or
something similar).
i am aware of the benefits i can draw from reading more code. actually,
that would be from reading more code written by somebody better than me
(at writing code, ofcourse). but this is always the case in the basic
erlang libraries. still, when time permits some thing like that, i
ususally write some code instead. stupid behaviour.


bengt
unknown
2003-12-16 10:58:58 UTC
Permalink
Post by unknown
2. As Dr. Joe (again congratulations Joe) points out "a+42" IS LEGAL ERLANG! Sorry for shouting but the point must be stressed. How can any one seriously suggest that the compiler disallow legal code? You are actually changing the semantics of the language when you do this. Are you aware of this fact?
Quite right.

As I pointed out before there can be two possible reasons why the
programmer wrote a+42.

1) The programmer made an error, or,
2) The programmer wants the system to raise an exception.

THERE IS NO WAY THE COMPILER KNOWS WHICH OF THESES TWO CASES APPLIES

(Sorry for shouting)

Conclusion:

You must tell the compiler (ie the compiler needs to know the
programmer's intention - << what I call intentionality >>)

There are two ways of telling the compiler:

a) add pragmas to Erlang

pragma(bad_code)
a+42
end

b) add a known wrapper that the compiler understands

for example make a function

this_is_not_an_error(X) -> X. and then call it thus:

this_is_not_an_error( a + 42)


Method b) needs no syntactic changes to the language, and (I guess)
a very simple change to the compiler.


If we adopt b) then the compiler should reasonably behave as follows:

a+42 Produces a warning
and compiles the code correctly

this_is_not_an_error(a+42) Compiles the code correctly
with no warning

In both cases the function is compiled - the only difference is
whether you warn or not.

Robert is entirely right - legal code must ALWAYS be correctly
compiled --- the compiler should never try to outguess the programmer
and guess that this was an error - half our test suits would stop
working if this were the case.

/Joe
unknown
2003-12-17 00:36:10 UTC
Permalink
Robert Virding wrote a mail quoting my mail, and he finished it as
... deleted ...
Sorry for getting worked up about this but I wish people would think
through their suggestions a bit more and consider the deeper
implications of them.
Finally one definite reason not to outlaw a+42 is that it is one of my
standard ways of generating an error in code, being much shorter to
write than exit(some_bogus_exit_value).
I will try to avoid the temptation of replying directly to the first
statement above, and instead reply to the following question of Robert.
3. What is the point is the compiler transforming it to
exit({badarith,...})? What are you exactly gaining by doing it?
Not saving code anyway, and hardly runtime either.
Ah, Robert, you seem to have very limited imagination... let me help
you here. Consider the code:

-module(a42).
-export([test/0]).

test() ->
catch f(),
ok.

f() when a+42 == 0 ->
%% extremely long sequence of code here
ok_1;
f() ->
Y = ackerman(), % very long computation here
% (without side-effects)
X = a+42,
mod:funct(X,Y),
%% possibly lots more other code here
ok_2.

ackerman() -> ....


QUESTION 1
----------
Do the "semantics" of Erlang (BTW, I would really like to see them
formally specified before people use this term again) allow a compiler
to transform the code above to just:

-module(a42).
-export([test/0]).

test() ->
ok.

QUESTION 2
----------
Can the savings in time and code space be made arbitrarily big, or not?

QUESTIONS 3-N
-------------
- Do the two occurrences of "a+42" need to be treated differently
by the compiler in the above example, or not?
- Is "a+42" an expression that always generates an error in Erlang?
(as far as the user is concerned)
- Is it just me that finds this Erlang-ism confusing to say
the least? (I.e., is this a user-friendly language design?)


Kostis.

PS. I am very well aware that the subject has shifted from
warnings vs. errors to something else now, but I could
not resist. Apologies.
unknown
2003-12-17 09:14:18 UTC
Permalink
Kostis Sagonas wrote:
...deleted
Post by unknown
- Is it just me that finds this Erlang-ism confusing to say
the least? (I.e., is this a user-friendly language design?)
i take the last question as beeing directed to any reader of the list,
and not to mr virding in particular. i apologise if i am mistaken.

i find erlang to be very user unfriendly in its syntax. the semantics
are beyond me, but some scope rules are terrible.


bengt
unknown
2003-12-17 04:22:27 UTC
Permalink
Is it clear to everyone that

"This code must raise an exception at run time"

and

"This code is in error"

are different? Let me give an Interlisp example first.

In Interlisp-D, there was a built-in function (SHOULDNT).
For example, you might define the factorial function like this:
[PUTDQ FACTORIAL
(LAMBDA (N)
(IF (> N 1) THEN (* N (FACTORIAL (- N 1)))
ELSE IF (>= N 0) THEN 1
ELSE (SHOULDNT]
Now the call to (SHOULDNT) must raise an exception at run time,
and if the call is _executed_ that indicates an error in the way
the function is used, but it isn't an error in the program to _have_
the call sitting there. An Interlisp compiler that rejected code
containing (SHOULDNT) would have rejected every Interlisp program I
ever wrote.

The C equivalent of (SHOULDNT) is abort(). Not, by the way, assert(0).

Is it also clear that

"This code is obviously silly"

and

"This code must raise an exception at run time"

are different? Lint warns about things that are clearly silly,
yet perfectly legal, and perfectly harmless. For example,
x == y;
as a C statement is perfectly well defined, and quite harmless,
yet lint warns about it because you probably didn't mean it.
A C compiler which refused to compile such a program would not
be helpful, it would be buggy.

It is possible to make (=)-for-(==) and (==)-for-(=) errors in
Erlang, and lint checking for Erlang might warn about them even
though they are legal.

Whether a particular piece of apparent silliness is worth warning
about is an empirical question: how often do programmers make the
kind of mistake that results in an instance of that pattern, and
how often do they intend it?

I have no idea what kinds of mistakes are commonest in Erlang.
It would be interesting to find out.

I've worked on a couple of compilers for languages that supported
exception handling. (I _think_ mine was the first paper on doing this
for Prolog, except that Quintus insisted on yanking it from the conference
after it was accepted.) Exception handling can be very tricky to get
right. (There's a debate raging in the Clean mailing list at this moment.)
ANSI Smalltalk exception handling is a case in point; to me it seems
insanely complicated, and it's not clear that Squeak has got it 100% right
yet. This means that especially when you are maintaining compilers, you
_need_ simple test cases which are certain to cause exceptions because
that's what you're testing.
unknown
2003-12-17 09:09:05 UTC
Permalink
Post by unknown
Is it clear to everyone that
"This code must raise an exception at run time"
and
"This code is in error"
are different?
...deleted


yes, i think i understand. you are saying that some code should be ok to
compile, even if it will always crash at run time.
would it seem very strange if i did not agree? because i do not, even
though i have not ever written a compiler. perhaps this is why?
Post by unknown
Is it also clear that
"This code is obviously silly"
and
"This code must raise an exception at run time"
are different?
yes, here we do agree. a large part of my code is obviously silly, and
yet there are not many run time exceptions. atleast not after i have run
the test cases.


bengt
unknown
2003-12-17 18:25:55 UTC
Permalink
On Wed, 17 Dec 2003 10:09:05 +0100
Post by unknown
yes, i think i understand. you are saying that some code should be ok
to compile, even if it will always crash at run time.
would it seem very strange if i did not agree? because i do not, even
though i have not ever written a compiler. perhaps this is why?
Bengt,

I could be wrong, but I think it is more likely that it's because you
aren't used to writing Erlang code in the "let it crash" style.

In this style, you write code very aggressively. You might have a
function like (this is just an example, not meant to do anything
useful:)

foo(Bar) ->
ok = file:setcwd(moo(Bar)),
{ok, File} = file:open(zoo(Bar)),
{ok, Records} = read_records(File),
ok = file:close(File),
Records.

So, the intent of the function is clearly to fail outright with
'badmatch' if anything goes wrong. foo/1's callers presumably wrap
their calls to foo (or their calls to whatever eventually calls foo) in
a catch. (Or the process that calls foo/1 is spawned, with the spawning
process linked/monitoring/trapping errors...)

The point is that foo/1 can fail, but that it's not *fatal*, in the
sense that the program keeps running.

Now what if we want a flag that disables this function? Maybe
eventually we'll read the flag from a file, but for testing purposes,
we'll just define it as a constant in the source:

baz_mode() -> true.

foo(Bar) ->
false = baz_mode(),
ok = file:setcwd(moo(Bar)), [...] Records.

Just as clearly as the previous example I hope, the intent of that line
of code is to disable the function if we are in baz mode, not to stop
the program from being compilable if we are in baz mode! :)

Where "let it crash" style is supported in Erlang, it's not always
graceful ('try' may improve that, if it ever gets added,) but it usually
results in much clearer code, because the assumptions/assertions are
made explicit.

But even if you don't use it, you have to recognize that many
programmers do, and for the "let it crash" style to work, code has to be
allowed to crash at runtime, even when it "can't be right".

-Chris
unknown
2003-12-17 20:17:35 UTC
Permalink
Post by unknown
foo(Bar) ->
ok = file:setcwd(moo(Bar)),
{ok, File} = file:open(zoo(Bar)),
{ok, Records} = read_records(File),
ok = file:close(File),
Records.
So, the intent of the function is clearly to fail outright with
'badmatch' if anything goes wrong. foo/1's callers presumably wrap
their calls to foo (or their calls to whatever eventually calls foo) in
a catch. [...]
Note that if foo/1's caller catches failures from read_records/1,
file descriptors will be leaked. Days later, the beam process runs
out of descriptors and strange things happen. Been there.

What would seasoned Erlangers recommend to avoid this ?

-- Pascal
unknown
2003-12-17 20:28:33 UTC
Permalink
On Wed, 17 Dec 2003 21:17:35 +0100
Post by unknown
Post by unknown
foo(Bar) ->
ok = file:setcwd(moo(Bar)),
{ok, File} = file:open(zoo(Bar)),
{ok, Records} = read_records(File),
ok = file:close(File),
Records.
So, the intent of the function is clearly to fail outright with
'badmatch' if anything goes wrong. foo/1's callers presumably wrap
their calls to foo (or their calls to whatever eventually calls
foo) in a catch. [...]
Note that if foo/1's caller catches failures from read_records/1,
file descriptors will be leaked. Days later, the beam process runs
out of descriptors and strange things happen. Been there.
What would seasoned Erlangers recommend to avoid this ?
Well, I am not a seasoned Erlanger[1], but I imagine most of them would
recommend spawning foo/1 into it's own process, then monitoring it or
linking to it. Then, when the process running foo/1 dies, its
filehandles and other resources will be deallocated (IIRC.)

-Chris

[1] I just come with a little wasabi on the side.
unknown
2003-12-18 09:29:06 UTC
Permalink
Chris Pressey wrote:
...deleted
Post by unknown
In this style, you write code very aggressively. You might have a
function like (this is just an example, not meant to do anything
useful:)
foo(Bar) ->
ok = file:setcwd(moo(Bar)),
{ok, File} = file:open(zoo(Bar)),
{ok, Records} = read_records(File),
ok = file:close(File),
Records.
in this case i think the compiler will complain about read_records/1
beeing unknown :-)

seriously, if i have a module which _only_ contains (exported) functions
that will always fail at runtime i do think the compiler should refuse
to compile it.
(btw: i think the compiler should refuse to compile a module without any
exported functions, too)
if some of the (exported) functions might work at runtime, then the
compiler should compile, but warn about the always failing functions.

...deleted
Post by unknown
But even if you don't use it, you have to recognize that many
programmers do, and for the "let it crash" style to work, code has to be
allowed to crash at runtime, even when it "can't be right".
yes, i am all for letting code crash. i just do not think it is usefull
to let the compiler produce code for a module that will always crash
because potentially correct code is written in such a way as to produce
a runtime error.

use
erlang:throw/1
or
erlang:exit/1
if an runtime error is what you want.


bengt
unknown
2003-12-18 20:03:58 UTC
Permalink
On Thu, 18 Dec 2003 10:29:06 +0100
Post by unknown
Post by unknown
But even if you don't use it, you have to recognize that many
programmers do, and for the "let it crash" style to work, code has
to be allowed to crash at runtime, even when it "can't be right".
yes, i am all for letting code crash. i just do not think it is
usefull to let the compiler produce code for a module that will always
crash because potentially correct code is written in such a way as to
produce a runtime error.
use
erlang:throw/1
or
erlang:exit/1
if an runtime error is what you want.
You're of course entitled to hold any opinion you wish, but I really
have to say that I disagree, and that I can't quite see your reasoning.

In Erlang as we know it, a + 42 generates an exception. An exception is
by definition not a show-stopper; it can be caught and acted upon. But
by changing it into a compile-time error, you're not even giving the
program an *opportunity* to crash. This runs against the "let it crash"
philosophy in my book.

Also, by forcing the programmer to write throw(blah) to cause an
exception, you're making them *make* it crash, which also runs counter
to "let it crash" - the programmer needn't exert such explicit effort.

To once again attempt to illustrate the difference:

foo() = (catch bar()).
bar() ->
true = some_assertion_which_may_or_may_not_be_constant(),
stuff().

...versus...

foo() = (catch bar()).
bar() ->
case some_assertion_which_may_or_may_not_be_constant() of
true -> stuff();
false -> throw(badarg)
end.

I'd much rather write (and read) the first version than the second.
Maybe it's just me, but I think it's more concise, more direct, and just
generally clearer. I also don't think it makes sense for the
compilation itself to succeed or fail based solely on whether
some_assertion_which_may_or_may_not_be_constant() is (detectably)
constant or not. That's why I'd much rather it be merely a compile-time
warning.

I'm all for getting as much information about possible errors as early
as possible - but I'm not in favour of dramatic shifts in how this
information would affect what is and what is not "legal Erlang".

-Chris
unknown
2003-12-19 07:37:40 UTC
Permalink
On the "let it crash" vein of the topic, I'm writing a SIP message parser. I ended up writing functions and code that only match positively with lexemes, letting the parser "crash" on mismatches, and catching to retry with alternative lexemes where multiple matches may occur. It makes the lexer *much* easier to read and debug!

In the end the parser exclusively uses only pattern matching and function clauses. No "if" or "case" or any other syntactic sugar. And it reads very clearly! (well, for me at least!)

Adding clauses containing exit, throw or returning {error,ErrCode} just adds clutter with no advantage whatsoever.

Pete.

On Thu, 18 Dec 2003 12:03:58 -0800
Post by unknown
On Thu, 18 Dec 2003 10:29:06 +0100
Post by unknown
Post by unknown
But even if you don't use it, you have to recognize that many
programmers do, and for the "let it crash" style to work, code has
to be allowed to crash at runtime, even when it "can't be right".
yes, i am all for letting code crash. i just do not think it is
usefull to let the compiler produce code for a module that will always
crash because potentially correct code is written in such a way as to
produce a runtime error.
use
erlang:throw/1
or
erlang:exit/1
if an runtime error is what you want.
You're of course entitled to hold any opinion you wish, but I really
have to say that I disagree, and that I can't quite see your reasoning.
In Erlang as we know it, a + 42 generates an exception. An exception is
by definition not a show-stopper; it can be caught and acted upon. But
by changing it into a compile-time error, you're not even giving the
program an *opportunity* to crash. This runs against the "let it crash"
philosophy in my book.
Also, by forcing the programmer to write throw(blah) to cause an
exception, you're making them *make* it crash, which also runs counter
to "let it crash" - the programmer needn't exert such explicit effort.
foo() = (catch bar()).
bar() ->
true = some_assertion_which_may_or_may_not_be_constant(),
stuff().
...versus...
foo() = (catch bar()).
bar() ->
case some_assertion_which_may_or_may_not_be_constant() of
true -> stuff();
false -> throw(badarg)
end.
I'd much rather write (and read) the first version than the second.
Maybe it's just me, but I think it's more concise, more direct, and just
generally clearer. I also don't think it makes sense for the
compilation itself to succeed or fail based solely on whether
some_assertion_which_may_or_may_not_be_constant() is (detectably)
constant or not. That's why I'd much rather it be merely a compile-time
warning.
I'm all for getting as much information about possible errors as early
as possible - but I'm not in favour of dramatic shifts in how this
information would affect what is and what is not "legal Erlang".
-Chris
--
"The Tao of Programming
flows far away
and returns
on the wind of morning."
unknown
2003-12-19 09:53:38 UTC
Permalink
Chris Pressey wrote:
...deleted
Post by unknown
In Erlang as we know it, a + 42 generates an exception. An exception is
by definition not a show-stopper; it can be caught and acted upon. But
by changing it into a compile-time error, you're not even giving the
program an *opportunity* to crash. This runs against the "let it crash"
philosophy in my book.
as i see it (ie, really subjective thinking will follow here):
i think ''let it crash'' is a good idea. i think it is such a good idea
that i want it to happen as soon as possible. in this case*, as soon as
possible is during compilation.

*quick reminder:
all exported functions in the module will crash, there are no
alternative ways for them to maybe succeed.
Post by unknown
Also, by forcing the programmer to write throw(blah) to cause an
exception, you're making them *make* it crash, which also runs counter
to "let it crash" - the programmer needn't exert such explicit effort.
i am advocating erlang:throw() as a better solution than ''a+42'' to
force a crash. i am not advocating erlang:throw() as a replacement for
mistyping ''A+42'' :-)


bengt
unknown
2003-12-19 09:04:58 UTC
Permalink
Post by unknown
Post by unknown
yes, i am all for letting code crash. i just do not think it is
usefull to let the compiler produce code for a module that will
always crash because potentially correct code is written in such a
way as to produce a runtime error.
use erlang:throw/1 or erlang:exit/1 if an runtime error is what you
want.
You're of course entitled to hold any opinion you wish, but I really
have to say that I disagree, and that I can't quite see your
reasoning.
In Erlang as we know it, a + 42 generates an exception. An exception
is by definition not a show-stopper; it can be caught and acted upon.
But by changing it into a compile-time error, you're not even giving
the program an *opportunity* to crash. This runs against the "let it
crash" philosophy in my book.
I think you're misunderstanding Chris. I don't remember him advocating
adding a
_ = throw ("No pattern matched for function foo")
clause to every function. He's advocating replacing
a + 42
with
throw ("Not Implemented Yet")
and I'd like to join in: `throw' tells the reader that the error was
intentional, and its argument provides information why the crash was
programmed and whether that's a temporary or permanent decision. That's
*far* better than `a + 42'.
`a + 42' is a cute trick - and cute tricks should be avoided since they
make software less maintainable, at least in my experience; software
should explicitly state what it does, and not rely on implicit behaviour
- I know that both constructing and deciphering cute tricks can give
enormous mental satisfaction, but the downside is that the code will be
readable for a slightly smaller fraction of programmers, and that's an
unacceptable side effect. IMHO.

Regards,
Jo
unknown
2003-12-17 09:26:42 UTC
Permalink
Post by unknown
The C equivalent of (SHOULDNT) is abort(). Not, by the way, assert(0).
If that's the case, SHOULDNT shouldn't be in the language.
After all, forcing the execution of the program to a halt is unmodular:
the caller might want to run the code and try an alternate strategy.
(Heck, that's what Erlang is all about: surviving faulty code that does
thing's that shouldn't happen.)
Post by unknown
Is it clear to everyone that
"This code must raise an exception at run time"
and
"This code is in error"
are different?
This classification isn't complete, and I think the ongoing discussion
is at least partially senseless because wrong distinctions of this kind
are constantly being made.
Actually, Erlang has more failure modes (and this list may not even be
complete):
1. Erroneous code that's rejected by the compiler. This includes stuff
like "1 = 2 3".
2. Code that will raise an exception. Example: "1 = 2".
3. Code that will terminate the process. Essentially, that's code that
doesn't catch all exceptions that may occur.
4. Code that will terminate the Erlang node.
5. Code that will terminate all communicating Erlang nodes.

Category 2 is what most confusion comes from: depending personal
definitions, one may consider it erroneous or not (and either definition
makes sense, depending on the point of view).

There's another source of confusion: Some people here argue that a
compiler shouldn't change the semantics of the language, but is that
indeed the case? Both syntax and semantics of Erlang have changed over
the years, in in an upwards-compatible fashion.
Post by unknown
Is it also clear that
"This code is obviously silly"
and
"This code must raise an exception at run time"
are different? Lint warns about things that are clearly silly,
yet perfectly legal, and perfectly harmless. For example,
x == y;
as a C statement is perfectly well defined, and quite harmless,
yet lint warns about it because you probably didn't mean it.
A C compiler which refused to compile such a program would not
be helpful, it would be buggy.
A C compiler would emit a warning and be compliant.

If I were to decide, I'd adopt a similar policy: issue warnings about
constructs like 1=2.

However, I dislike somethine entirely different about them. There's
clearly a need to make a computation fail, and different people use
different idioms to express is. Some say 1=2, others say a+0, and I saw
mentions a handful others.
While that's neat, it's also an unnecessary maintenance obstacle.
Whenever a maintainer sees such code, he has to (a) find out what this
code does (which will take more time than usual because he'll have to
revisit the usual assumption that code isn't written for raising
exception), (b) figure out whether the code is raising the exception
intentionally or not.
There are two additional disadvantages: (c) it's impossible to do a grep
to see whether any temporary exception-raisers were accidentally left in
some code that's considered ready for production release, and (d) if the
code does raise an exception, it will give the maintainer wrong
information: the true error is that the system is trying to execute
unwritten code, but the system will give a different error message.

I'm quite mystified why there isn't a "niy" (not implemented yet) or
"tbd" (to be determined) routine, that explicitly raises an exception
saying "fatal error: trying to execute code that has not yet been
written" (or something similar).

Once such a routine is in place, the question whether changing the
semantics of obviously silly constructs is a no-brainer: if there's no
useful purpose for them, their semantics is irrelevant and can be
changed. One may adopt a more conservative approach and maintain
compatibility with legacy code, so a warning would probably be more
appropriate. An even better option would be if that warning could be
turned into an error for those shops who want strict discipline (not all
shops need that, but sometimes there are good reasons to turn on the
equivalent of -Wall --warnings-as-errors).
Post by unknown
It is possible to make (=)-for-(==) and (==)-for-(=) errors in
Erlang, and lint checking for Erlang might warn about them even
though they are legal.
Whether a particular piece of apparent silliness is worth warning
about is an empirical question: how often do programmers make the
kind of mistake that results in an instance of that pattern, and
how often do they intend it?
And, more importantly: how easy is it to make the compiler detect that
pattern, and how likely is it that the compiler will make errors when
identifying such patterns?
Post by unknown
I've worked on a couple of compilers for languages that supported
exception handling. (I _think_ mine was the first paper on doing this
for Prolog, except that Quintus insisted on yanking it from the conference
after it was accepted.) Exception handling can be very tricky to get
right. (There's a debate raging in the Clean mailing list at this moment.)
ANSI Smalltalk exception handling is a case in point; to me it seems
insanely complicated, and it's not clear that Squeak has got it 100% right
yet. This means that especially when you are maintaining compilers, you
_need_ simple test cases which are certain to cause exceptions because
that's what you're testing.
There's always the explicit exception raising statement. Any language
that has exceptions should have such a statement, and Erlang does it right.

Actually, I don't think that generating a test case should be a problem,
ever. There's always the possibility to do unit testing. You'll have to
structure the compiler so that it has units to be tested, but this
restructuring will clean up the compiler considerably, making is more
stable and reliable, so this is a good idea anyway. Loss of test cases
due to changes in language syntax or semantics is just an indicator that
the compiler is badly structured IMNSHO.

Regards,
Jo
unknown
2003-12-17 11:08:23 UTC
Permalink
Post by unknown
Robert Virding wrote a mail quoting my mail, and he finished it as
... deleted ...
Sorry for getting worked up about this but I wish people would think
through their suggestions a bit more and consider the deeper
implications of them.
Finally one definite reason not to outlaw a+42 is that it is one of my
standard ways of generating an error in code, being much shorter to
write than exit(some_bogus_exit_value).
I will try to avoid the temptation of replying directly to the first
statement above, and instead reply to the following question of Robert.
3. What is the point is the compiler transforming it to
exit({badarith,...})? What are you exactly gaining by doing it?
Not saving code anyway, and hardly runtime either.
Ah, Robert, you seem to have very limited imagination
<<<< limited imagination -- never - I've never heard anybody accuse
Robert of limited imagination before -- he has his little quirks, we
all do, but limited imagination is not among them... deciding to
totally rewrite the compiler one week before it's to be released and a
month after the last final-final code stop might be a more appropriate
failing :-) >>>>
Post by unknown
... let me help
-module(a42).
-export([test/0]).
test() ->
catch f(),
ok.
f() when a+42 == 0 ->
%% extremely long sequence of code here
ok_1;
f() ->
Y = ackerman(), % very long computation here
% (without side-effects)
X = a+42,
mod:funct(X,Y),
%% possibly lots more other code here
ok_2.
ackerman() -> ....
QUESTION 1
----------
Do the "semantics" of Erlang (BTW, I would really like to see them
formally specified before people use this term again) allow a compiler
-module(a42).
-export([test/0]).
test() ->
ok.
ABSOLUTELY NOT - the programs mean *completely* different things.

In your example the compiler writers job is to make sure that
ackerman() gets evaluated as quicky as possible - it is NOT to change
the meaning of the program.

Programs are ONLY correct wrt to specifications - period - end of story.

Here is the spec (which you didn't show us :-)

<<

Write me a program, that performs a long and evolved computation
(say by calling the ackerman function) - then generate an error and
return ok.

This is to be used in a regression test where we test the speed-up
introduce by our new big-num acceleration hardware.
One good property that a programming language should have is
"predictability" the programmer should be able to naively understand
the (space-time) consequences of their code - break this rule and you
are in trouble.

I *hate* compilers that make assumptions about my code and optimize
away my so-called "mistakes".

If the programmer tells the system to do something stupid then it
should do something stupid.

<< aside -- why is this: Once upon a time I had to program a "radar
controller" when I worked for the Eiscat scientific association. The radar
controller had lots of lamps on the front panel.

The hardware guys wanted me to write an *incorrect* program which
would cause the red error lamp on the front panel to light up.

This proved *impossible* because a "smart" compiler refused all
programs which were incorrect and thus I could not write a test
program that light the red lamp.

I struggled for weeks - eventual we had to throw away all the
control software and re-write it from scratch so that I could write
"incorrect" programs.

Moral - the system should do *exactly* what you tell it and nothing
else >>
Post by unknown
QUESTION 2
----------
Can the savings in time and code space be made arbitrarily big, or not?
No - the compiler/run-time system should make the code do exactly
what the programmer said the code should do - what one should optimize
are the artificial artifacts introduced by compilation - ie minimize
garbage collection times, instruction dispatching times etc.
Post by unknown
QUESTIONS 3-N
-------------
- Do the two occurrences of "a+42" need to be treated differently
by the compiler in the above example, or not?
No
Post by unknown
- Is "a+42" an expression that always generates an error in Erlang?
(as far as the user is concerned)
Yes
Post by unknown
- Is it just me that finds this Erlang-ism confusing to say
the least? (I.e., is this a user-friendly language design?)
I don't find this strange at all - I guess it's just you :-)

/Joe
Post by unknown
Kostis.
PS. I am very well aware that the subject has shifted from
warnings vs. errors to something else now, but I could
not resist. Apologies.
unknown
2003-12-17 19:49:17 UTC
Permalink
I've been following this thread with interest.

My gut feeling is that it's a can of worms that's best left closed. Why
warn about this case, but not slightly more complex cases? Depending on the
kind of optimizations the compiler does, you might be able to get warnings
about some of the these, but you'll still never get constant propagation
across module boundaries, for example. And "a + 42" as a legitimate mistake
on the programmer's part is much, much rarer than a simple fat-fingering of
the name of a function in a another module and having the compiler accept it
anyway (note: I'm not suggesting that this should be addressed!).

James
unknown
2003-12-17 20:31:39 UTC
Permalink
And "a + 42" as a legitimate mistake on the programmer's part is
much, much rarer than a simple fat-fingering of the name of a
function in a another module and having the compiler accept it
anyway (note: I'm not suggesting that this should be addressed!).
I guess xref addresses this.

There's also a compiler option I've been wanting that could handle
it. I would like an option to generate a M:module_info(calls) that
returns a list of calls made by each function in the module.

Returns: [{Function, Arity, [Callee]}]
Callee = {Module, Function, Arity}

This way it would be easy to write a program that loads all modules in
the code path and reports all calls to functions that can't be found.

(I must admit that I rarely have this particular problem, possibly
because I use M-TAB and M-/ to complete my function names.)

I really want it as a way to write a who_calls(M,F,A)->[{M,F,A}]
function that could be used by Emacs for a "reverse tags lookup".
who_calls would expect all relevant modules to be loaded and do a fold
over them to find all callers. Then I can present the results in a
"hyperlinked" buffer and save myself a lot of grep'ery.

I've been meaning to hack this up as a parse transform using
syntax_tools and use it in the Jungerl. But if it's something the OTP
group would be interested in, I could look at putting it in the
compiler..?

-Luke
unknown
2003-12-18 09:15:51 UTC
Permalink
Luke Gorrie wrote:
...deleted
Post by unknown
There's also a compiler option I've been wanting that could handle
it. I would like an option to generate a M:module_info(calls) that
returns a list of calls made by each function in the module.
Returns: [{Function, Arity, [Callee]}]
Callee = {Module, Function, Arity}
This way it would be easy to write a program that loads all modules in
the code path and reports all calls to functions that can't be found.
this would be a very good thing to have as a helper. today i manage by
cheating. i am not using the code path, but the source code directly.
Post by unknown
(I must admit that I rarely have this particular problem, possibly
because I use M-TAB and M-/ to complete my function names.)
in wily one can cut-and-paste with a mouse chord. it is rare to write
any text more than once. different solution to the same problem.
Post by unknown
I really want it as a way to write a who_calls(M,F,A)->[{M,F,A}]
function that could be used by Emacs for a "reverse tags lookup".
who_calls would expect all relevant modules to be loaded and do a fold
over them to find all callers. Then I can present the results in a
"hyperlinked" buffer and save myself a lot of grep'ery.
would this find:
erlang:apply( module, function, [arg]),

my current grep on the source will find it.

and ofcourse, the real problem:
Module:Function(Arg)

any ideas on how to find these without running the code?


bengt
unknown
2003-12-24 20:44:39 UTC
Permalink
Post by unknown
I really want it as a way to write a who_calls(M,F,A)->[{M,F,A}]
function that could be used by Emacs for a "reverse tags lookup".
who_calls would expect all relevant modules to be loaded and do a fold
over them to find all callers. Then I can present the results in a
"hyperlinked" buffer and save myself a lot of grep'ery.
CCviewer does this, and also who_uses_record(R) and who_uses_macro(M).
It keeps a RAM database which is incrementally updated as it discovers
that source modules have changed. There are some built-in analyses
that can be used through the web interface, like finding cyclical
dependencies between modules (or functions), and listing all users
of a particular application.

I've been meaning to provide an API other than the web interface to
facilitate static analysis on the fly, perhaps from Distel. What's
been stopping me is that my maintenance effort for CCviewer is ~0
right now, and that suits me just fine. So unless someone really
wants to start using it and doing weird and wonderful stuff with it,
it stays the way it is.

/Uffe
--
Ulf Wiger
unknown
2003-12-17 23:01:18 UTC
Permalink
Post by unknown
I've been following this thread with interest.
My gut feeling is that it's a can of worms that's best left closed.
Why warn about this case, but not slightly more complex cases?
Sorry to suddently turn this thread into a political one, but the
above argument seems to me an argument of the form:

"Why try to eliminate some social injustices, since we are never
going to eliminate them all (especially the most subtle ones)."

Just think about it...

Cheers,
Kostis.
unknown
2003-12-17 23:32:12 UTC
Permalink
Post by unknown
Post by unknown
My gut feeling is that it's a can of worms that's best left closed.
Why warn about this case, but not slightly more complex cases?
Sorry to suddently turn this thread into a political one, but the
"Why try to eliminate some social injustices, since we are never
going to eliminate them all (especially the most subtle ones)."
Just think about it...
First, you can't compare software and social injustices. You just can't.
Well, okay, you just did, but it's not meaningful :)

Second, software is a battle against complexity. In this particular case
you're adding a check and a warning about something that's (A) a rare
occurrence, (B) going to bomb out immediately the first time you test the
function, and (C) detected at compile time in specific circumstances and not
detected in others. To provide a warning for this, you are adding
additional code to the compiler. Is it worth it?

I would buy that, yes, it makes sense because "a + 42" is a typical newbie
mistake, and we're probably not talking about a lot of additional code to
warn about this. But it's also a typical newbie mistake to use an atom in a
function header, like "first({x,y}) -> x", and that can't be warned against.
So it's hit-or-miss about what can be warned about and what can't. This is
what I meant about a can of worms. It is more straightforward to dismiss
the issue, rather than going down a long road of adding warnings for all
kinds of things that may or may not be errors, because realistically this is
a minor issue.

Again, this has nothing to do with social injustices. You don't engineer
laws the same way you engineer software.

James
unknown
2003-12-17 23:52:45 UTC
Permalink
Post by unknown
Post by unknown
"Why try to eliminate some social injustices, since we are never
going to eliminate them all (especially the most subtle ones)."
Just think about it...
First, you can't compare software and social injustices. You just can't.
First of all, I am not comparing them with "software" but with
"software errors". To me, the analogy is a very good one.
In the same way that we cannot eliminate all social injustices,
we cannot eliminate all software errors. However, trying to is
a worthwhile cause, and achieving partial success is probably
better than doing nothing about it. The analogy is even better
if you take into account that most often than not, there is no
unanimous agreement about what really constitutes an injustice
or an error.
Post by unknown
Second, software is a battle against complexity. In this particular case
you're adding a check and a warning about something that's (A) a rare
occurrence, (B) going to bomb out immediately the first time you test the
function, and (C) detected at compile time in specific circumstances and not
detected in others. To provide a warning for this, you are adding
additional code to the compiler. Is it worth it?
You are obviously not a compiler writer, are you? In this particular
case, we are actually talking about *eliminating* (in the case of the
BEAM compiler) and *avoiding adding* (in the case of HiPE) lots of
stupid code that is now special-handling the case of "a+42" (and
generating instructions with very weird argument types). If you
do not believe me, just look at the sources.

Best,
Kostis.
unknown
2003-12-18 14:26:05 UTC
Permalink
Post by unknown
In this particular
case, we are actually talking about *eliminating* (in the case of the
BEAM compiler) and *avoiding adding* (in the case of HiPE) lots of
stupid code that is now special-handling the case of "a+42" (and
generating instructions with very weird argument types). If you
do not believe me, just look at the sources.
But you're only getting this win if you make this an error, not a warning,
right? You'll still be "generating instructions with weird argument types"
if you print a warning, then generate code. In fact, you're *adding* (what
I suspect is a trivial amount of) code to print the warning.

James
unknown
2003-12-18 14:32:16 UTC
Permalink
Post by unknown
But you're only getting this win if you make this an error,
not a warning, right?
It should then come as to no suprise that I have made a case
for the error over the warning in a previous mail of mine.

Kostis.
unknown
2003-12-18 14:55:43 UTC
Permalink
When you print the error message, do you intend to
print the line number of the offending expression in the
source code?

/Bjorn
Post by unknown
Post by unknown
But you're only getting this win if you make this an error,
not a warning, right?
It should then come as to no suprise that I have made a case
for the error over the warning in a previous mail of mine.
Kostis.
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2003-12-18 15:52:14 UTC
Permalink
When you print the error message, do you intend to print the
line number of the offending expression in the source code?
If possible, of course.

When HiPE compiles code as a JIT (i.e. starting from BEAM code)
this is not possible. When hipe compiles starting from source
through Core Erlang, the line numbers will of course be printed.

Kostis.
unknown
2003-12-22 03:58:44 UTC
Permalink
Kostis Sagonas <kostis> wrote:
Sorry to suddently turn this thread into a political one, but the
above argument seems to me an argument of the form:

"Why try to eliminate some social injustices, since we are never
going to eliminate them all (especially the most subtle ones)."

Just think about it...

Well yes, I have thought about it, and the analogy is invalid.
A much more interesting and possibly more fruitful analogy is to
the "human factors and safety of interfaces" stuff one often sees
popping up in comp.risks. If you automate too much, people get to
rely on the machine, and then the automation has to be _really_ good.
If you have alarms that keep going off, people start ignoring them,
and then really bad things happen, because some of the alarms aren't
false. It seems that there's an optimal level of human involvement
in checking; too much and people can't do it, too little and people
do even less than they should. It is important for people to have
a clear understanding of what the machine will check (so they don't
have to) and what it won't (so they DO have to).

A question I asked a couple of times in this year's functional programming
exam paper had the general form
- description of data structure
- set up the type declarations in Haskell!
- how much of the data structure invariants were you able
to tell the Haskell compiler about, and why?
- finish the job!

Whatever you end up doing, it is a human factors disaster if the
BEAM compiler and the HiPE compiler disagree, because then people will
not be able to form a coherent mental model of what will be checked
by machine and what they must check in their inspections.
unknown
2003-12-30 23:36:23 UTC
Permalink
This discussion just goes on :-)

I still have not seen any good reasons to change my viewpoint, in fact I have got a few to become even more restrictive:

1. a+42, 1=2, etc are all legal expressions even if they generate errors when evaluated. As errors have meaning in Erlang then you can't just forbid them.

2. Many of the warnings generated by the compiler have been chosen in an extremely ad hoc fashion, they were deemed useful by someone and easy to implement. There is really no clear policy for what is checked and what is not. I mean why check the arguments to io:format and not a host of other common library functions. Especially considering that io:format is easy to guard against. Richard O'Keefe wrote some good stuff about the problems of getting the right level of checking.

3. With this in mind then Bjorn's new checks are probably a bad idea, the warnings become even more ad hoc and difficult to understand. I wonder if maybe some of the existing warnings should be removed. I still say that many of the warnings give very little and some are a right pain.***

4. You cannot have the option of forbidding legal code, no matter how stupid it may seem. The compiler must handle all legal code. Trying to guess and saying that 90% of the time the user wrote this then they made an error is not good enough. This is similat to the syntax change suggestion concerning =<< made earlier.

5. I wonder if it is good for the compiler to try and use warnings to enforce a certain programming style?

*** Two warnings that really anoy me are unused vars and unused functions.

I seldom use '_', the anonymous variable, but prefer to give all variables names so I know what they are, even if I don't use them later. I DON'T LIKE the _Varname convention so why should the compiler try and enforce. Of course the alternative would be to ALWAYS use it. That would teach the compiler to mind its own business. :-)

When I define a data structure and its access functions I always define full range of access functions, even those which are not used. I think the structure and how it is intended to be used becomes clearer then. Having the compiler burn me with warnings is irritating to say the least. Yes I can wrap the unused ones in comments but then I don't get any checking. This has been done to me in the compiler code. Anyway what is wrong with defining unused functions? They don't hurt anyone and the compiler removes them from the code anyway.


Robert
unknown
2004-01-07 12:38:58 UTC
Permalink
Post by unknown
This discussion just goes on :-)
1. a+42, 1=2, etc are all legal expressions even if they generate errors when evaluated. As errors have meaning in Erlang then you can't just forbid them.
I agree.
Post by unknown
2. Many of the warnings generated by the compiler have been chosen in an extremely ad hoc fashion, they were deemed useful by someone and easy to implement. There is really no clear policy for what is checked and what is not. I mean why check the arguments to io:format and not a host of other common library functions. Especially considering that io:format is easy to guard against. Richard O'Keefe wrote some good stuff about the problems of getting the right level of checking.
I disagree.

The warnings for io:format were implemented because many users often DID
make mistakes. Furthermore, io:formats in error handling might not have been
properly tested, so the first time the error handling code was actually executed
it crashed.

Also, it was not particularily easy to add the io:format code. We have had
quite a bit work keeping the checks correct.

So that is bad example of something we added because it was easy.
Post by unknown
3. With this in mind then Bjorn's new checks are probably a bad idea, the warnings become even more ad hoc and difficult to understand. I wonder if maybe some of the existing warnings should be removed. I still say that many of the warnings give very little and some are a right pain.***
In old versions of OTP, there were warnings for imports that you didn't use
and for exporting variables out of case statements. We have turned off those
warnings by default.
Post by unknown
4. You cannot have the option of forbidding legal code, no matter how stupid it may seem. The compiler must handle all legal code. Trying to guess and saying that 90% of the time the user wrote this then they made an error is not good enough. This is similat to the syntax change suggestion concerning =<< made earlier.
I agree. We don't forbid legal code, we just warn about it.
Post by unknown
5. I wonder if it is good for the compiler to try and use warnings to enforce a certain programming style?
*** Two warnings that really anoy me are unused vars and unused functions.
I seldom use '_', the anonymous variable, but prefer to give all variables names so I know what they are, even if I don't use them later. I DON'T LIKE the _Varname convention so why should the compiler try and enforce.
Even if YOU don't like the _Varname convention, other people love it.

Since we started to use the warn_unused_vars option, we have found many
minor bugs in the OTP code. I have also found several bugs in Wings.

That was in WORKING code; in the OTP code we also have extensive test suites.

When I write new code, the warnings immediately inform me about a bug that
otherwise could have taken me some time to find by testing.
Post by unknown
Of course the alternative would be to ALWAYS use it. That would teach the compiler to mind its own business. :-)
You can also use the nowarn_unused_vars option.
Post by unknown
When I define a data structure and its access functions I always define full range of access functions, even those which are not used. I think the structure and how it is intended to be used becomes clearer then. Having the compiler burn me with warnings is irritating to say the least. Yes I can wrap the unused ones in comments but then I don't get any checking.
There should really be a way to turn off the warnings for unused functions.
We might add a mechanism in R10 for doing that.
Post by unknown
This has been done to me in the compiler code.
As we don't want to see the warning in our daily builds, we had to comment
out the functions.
Post by unknown
Anyway what is wrong with defining unused functions? They don't hurt anyone and the compiler removes them from the code anyway.
When maintaining other people's code (I am NOT talking about the compiler
here), you often find stray functions that are left from a previous version
of the module, but is no longer used.


To summarize: IMO, every warning that helps you to find more bugs are
useful.

/Bjorn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2004-01-14 08:05:08 UTC
Permalink
Post by unknown
Post by unknown
This discussion just goes on :-)
3. With this in mind then Bjorn's new checks are probably a bad idea, the warnings become even more ad hoc and difficult to understand. I wonder if maybe some of the existing warnings should be removed. I still say that many of the warnings give very little and some are a right pain.***
In old versions of OTP, there were warnings for imports that you didn't use
and for exporting variables out of case statements. We have turned off those
warnings by default.
Warning about exporting variables out of if/case/receive statements is
one one I would have left in as it is clearly dangerous. :-)
Post by unknown
Even if YOU don't like the _Varname convention, other people love it.
Fine, but why force it on me by default? Leave the warning in there but
let people who want it turn it on explicitly. Having it as default means
you are trying to enforce a certain style.
Post by unknown
There should really be a way to turn off the warnings for unused functions.
We might add a mechanism in R10 for doing that.
Defintely add an option to turn warning about unused functions on and
off, but have the default value OFF.

Robert
unknown
2004-01-14 08:11:00 UTC
Permalink
Post by unknown
Warning about exporting variables out of if/case/receive statements is
one one I would have left in as it is clearly dangerous. :-)
But today it's an _error_ to unsafely export from if/case/receive,
right?

Fanning the flames :-),
Luke
unknown
2004-01-14 10:46:57 UTC
Permalink
Post by unknown
Warning about exporting variables out of if/case/receive statements is
one one I would have left in as it is clearly dangerous. :-)
There is still a warning if you use the exported variable in
a matching operation, because THAT is usually dangerous.

If you use the exported variable in an expression, you get no
warning.
Post by unknown
Post by unknown
Even if YOU don't like the _Varname convention, other people love it.
Having it as default means you are trying to enforce a certain style.
Yes, we are. :)

/Bjorn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2003-12-31 17:07:45 UTC
Permalink
Post by unknown
2. Many of the warnings generated by the
compiler have been chosen in an extremely
ad hoc fashion, they were deemed useful by
someone and easy to implement. There is
really no clear policy for what is checked
and what is not. I mean why check the
arguments to io:format and not a host of
other common library functions.
I tried to make this point in an earlier message, though I may not
have succeeded. I don't really want to see the compiler cluttered up
with a lot of checks for things that may or may not be errors,
especially when:

1. There are many more things that are not checked (for example, any
place where a newbie types "x" instead of "X"). Trying to catch them
all seems like a long and unproductive road to go down. (Okay, I
once complained about list comprehensions without generators being
quietly compiled, but I'd argue that's invalid Erlang.)

2. Simple, interactive testing catches all of these errors anyway,
even the ones that aren't warned about.

Additionally, I think Erlang--both the language and implementation--
are on the verge of becoming too complicated. I think both could use
some streamlining. Adding ad hoc warnings is a step in the other
direction.
unknown
2003-12-31 19:53:12 UTC
Permalink
Post by unknown
2. Simple, interactive testing catches all of these errors anyway,
even the ones that aren't warned about.
Experiences users will find them.
Warnings are useful for those things that newbies often fall trap to:
these should be warnings. Or at least warnings that can be switched on
using a compiler option.

Just my 2c.

Regards,
Jo
unknown
2004-01-07 13:01:23 UTC
Permalink
Post by unknown
Post by unknown
2. Many of the warnings generated by the
compiler have been chosen in an extremely
ad hoc fashion, they were deemed useful by
someone and easy to implement. There is
really no clear policy for what is checked
and what is not. I mean why check the
arguments to io:format and not a host of
other common library functions.
I tried to make this point in an earlier message, though I may not
have succeeded. I don't really want to see the compiler cluttered up
with a lot of checks for things that may or may not be errors,
1. There are many more things that are not checked (for example, any
place where a newbie types "x" instead of "X"). Trying to catch them
all seems like a long and unproductive road to go down. (Okay, I
once complained about list comprehensions without generators being
quietly compiled, but I'd argue that's invalid Erlang.)
Our intention is not to try to catch all possible errors.

The new warnings I've added are emitted by the optimization passes.
Instead of silently removing a clause that cannot possible match,
a warning is produced. Thus, there is not a lot of extra code in the
compiler.

There were many warnings in the OTP code for clauses that could not match.
Most of them harmless, of course, since the code is working, but the
redundant clauses are confusing for anyone reading the code.

I found a few redundant clauses in the compiler itself.
Post by unknown
2. Simple, interactive testing catches all of these errors anyway,
even the ones that aren't warned about.
IMO, everything that help you find more bug is useful. When working
against a tight dead-line, most people forget to do the simple test,
to "save time".
Post by unknown
Additionally, I think Erlang--both the language and implementation--
are on the verge of becoming too complicated. I think both could use
some streamlining. Adding ad hoc warnings is a step in the other
direction.
I see your point, but I consider the new warnings to be useful,
and they did not add much complexity to the compiler. Maybe reduced
the complexity, as I could clean up part of the code at the same time.

/Bjorn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2003-12-31 23:05:29 UTC
Permalink
Post by unknown
Warnings are useful for those things that
newbies often fall trap to: these should be
warnings. Or at least warnings that can be
switched on using a compiler option.
I agree in principle, but then you see newbies making errors that you
can't check for:

second([x,y|t]) -> y.

Where x, y, and t were intended to be variables, not atoms. Or using
"/" instead of "div". Or the more common case of calling an external
function with the wrong number of parameters, or simply a non-
existant external function (which, I admit, would be nice to check
for, though not so simple). It's a slippery slope to try to fix all
of these at compile time, and it isn't clear that an overly pedantic
compiler is a worthy goal. There should at least be consistent
principles for what is warned about and what isn't.

That said, I still like the new warning for "This statement will
never match," maybe just because I've seen similar warnings in ML-
like languages.
unknown
2004-01-01 13:06:58 UTC
Permalink
Post by unknown
Post by unknown
Warnings are useful for those things that
newbies often fall trap to: these should be
warnings. Or at least warnings that can be
switched on using a compiler option.
I agree in principle, but then you see newbies making errors that you
Right.
It's impossible to flag all newbie errors anyway. But every error that
gets flagged will make the learning curve less steep, which is generally
a Good Thing IMHO.
Once people get used to the issues (iow as soon as the warnings become
more annoying than helpful), they'll turn warnings off.

Just my 2c, back to lurk mode :-)
Regards,
Jo
unknown
2005-02-07 12:07:43 UTC
Permalink
I have some good news and some bad news. :-)
The good news is that we can change lists:map/2 and friends in
R10B-4 to reject an F that is not a fun. That is merely a bug fix
and does not break backward compatibility.
The bad news is that F must be allowed to be a "tuple fun" too,
to keep backward compatibility.
map(F, [H|T]) ->
[F(H)|map(F, T)];
map(F, []) when is_function(F) -> %% andalso is_fun_arity(F) == 1
[];
map({M,F}, []) when is_atom(M), is_atom(F) ->
[].
I totally understand the argument for maintaining backwards compatibility,
even though I do not fully agree with it, but if the decision is to add
the extra clause, there is still something which is needed.

Besides checking that M and F are atoms, there needs to be a check that
the {M,F} is a valid fun object and its arity is 1 (note the is_fun_arity
new guard that is required in the second clause -- I wrote it as a comment
but it really needs to be introduced in the language for the type inference
algorithm to infer that the F in the first and the second clause are of
the same type).

Kostis
unknown
2005-02-07 13:56:50 UTC
Permalink
Kostis Sagonas <kostis> writes:
[..]
Post by unknown
Besides checking that M and F are atoms, there needs to be a check that
the {M,F} is a valid fun object and its arity is 1 (note the is_fun_arity
new guard that is required in the second clause -- I wrote it as a comment
but it really needs to be introduced in the language for the type inference
algorithm to infer that the F in the first and the second clause are of
the same type).
Unfortunately, it is tricky to check that {M,F} is a valid fun. The module
M might not even be loaded.

It has been suggested earlier that we should add

fun M:F/Arity

to the language. Unfortunately, we didn't add it in R10B. We could add
it in R11B.

Regarding the arity test for a "real" fun, we could add the guard BIF

is_function(F, Arity)

in R11B.

/Bj?rn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2005-02-07 21:22:11 UTC
Permalink
Hi!
Post by unknown
Unfortunately, it is tricky to check that {M,F} is a valid fun. The module
M might not even be loaded.
I do not see this as a grave problem. It might be handled as if the
programmer had written fun(X) -> M:F(X) end instead.
Post by unknown
It has been suggested earlier that we should add
fun M:F/Arity
to the language.
Or this :-)

Take care,

Carsten
--
Carsten Schultz (2:38, 33:47)
http://carsten.codimi.de/
PGP/GPG key on the pgp.net key servers,
fingerprint on my home page.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 189 bytes
Desc: Digital signature
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20050207/7b28ed7e/attachment.bin>
unknown
2005-02-22 19:39:56 UTC
Permalink
Post by unknown
Unfortunately, it is tricky to check that {M,F} is a valid fun. The module
M might not even be loaded.
It has been suggested earlier that we should add
fun M:F/Arity
to the language. Unfortunately, we didn't add it in R10B. We could add
it in R11B.
I think it would be nice if this prints as something like:

#Fun<M:F/Arity>

so that you can easily see where it's pointing.

I also wonder if people are using {M,F}-funs without fixed arity.
toy example:

callback(Values, Callback = {M,F}) when list(Values) ->
apply(M, F, Values).

Cheers,
Luke
unknown
2005-02-07 13:46:53 UTC
Permalink
Post by unknown
Besides checking that M and F are atoms, there needs to be a
check that the {M,F} is a valid fun object and its arity is 1
(note the is_fun_arity new guard that is required in the second
clause -- I wrote it as a comment but it really needs to be
introduced in the language for the type inference
algorithm to infer that the F in the first and the second
clause are of the same type).
Fine, but that should not be an inner loop check.

BTW, can is_fun_arity() really be a guard function?
With on-demand code loading, is_fun_arity can only
say whether a given function has the right arity if
it's already loaded - otherwise, it must fail - or
the guard check potentially hang while the considerable
side effect of loading the module into memory is
executed. I assume that this will not be allowed.

This would mean that lists:map/2 could fail even if
the function {M,F} exists and has the right arity,
which would seem to violate Joe's Principle of Least
Astonishment.

Given that {M,F} must point to an exported function,
the BIF erlang:function_exported/3 seems to do pretty
much what you're asking for, except for a few oddities:

Eshell V5.4.3 (abort with ^G)
1> lists:map({erlang,abs},[-2,-1,0,1,2,3]).
[2,1,0,1,2,3]
2> erlang:function_exported(erlang,abs,1).
false
3>

The documentation says the following:

"erlang:function_exported(Module, Function, Arity)

Returns true if the module Module is loaded and contains
an exported function Function/Arity; otherwise false.

Returns false for any BIF (functions implemented in C
rather than in Erlang).

This function is retained mainly for backwards
compatibility.
It is not clear why you really would want to use it."


Note that this function is not allowed in guards.

PS
4> erlang:function_exported(erlang,send_nosuspend,3).
true
4> erlang:function_exported(erlang,send_nosuspend,3).
true
5> erlang:function_exported(ets,insert,2).
false
6> erlang:function_exported(ets,tab2list,1).
true
7> erlang:function_exported(lists,append,2).
true
8> erlang:function_exported(erlang,yield,0).
true

The rule of returning 'false' for BIFs pretty much makes
this BIF unusable for practical purposes. In the examples
above, it's not even enough to look into the source code
to predict what this function will return. The Erlang source
for erlang:yield() looks like this:

yield() ->
erlang:yield().

Go figure. (:

/Uffe
unknown
2005-02-07 13:59:27 UTC
Permalink
"Ulf Wiger \(AL/EAB\)" <ulf.wiger> writes:
[...]
Post by unknown
BTW, can is_fun_arity() really be a guard function?
Yes, it can if it only accepts "real" funs, not tuple funs.

/Bj?rn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2005-02-07 19:38:58 UTC
Permalink
Post by unknown
Unfortunately, it is tricky to check that {M,F} is a valid fun.
The module M might not even be loaded.
This is true, but the above yet one more argument for finally
deciding to discontinue support for the {M,F} fun notation.

It ain't fun!

Kostis
unknown
2005-02-08 00:42:30 UTC
Permalink
Scheme insists on the list argument of map being a proper list, and it
seems to cause no trouble there.
Kostis replied:

I am not sure I get this, because map naturally protects itself from
inproper lists in its second argument.

It is not true that "map naturally protects itself. In Scheme,

(define (map F L)
(if (pair? L)
(cons (F (car L)) (map F (cdr L)))
'()))

is the most obvious way to implement map. You have to go out of your way
to explicitly check for an empty list. And that's my point: in a case
where it would not just be _as_ easy to be "generous" but it would actually
be _easier_ to be "generous", Scheme demands a proper list.

Can it be that you are mixing
map with some other function like append?

No. append does *not* require its second argument to be a proper list in
Scheme implementations. There is an interesting but obvious reason:

- in cases where the only burden is one extra check "now that this
argument isn't a pair, is it an empty list?", the check is done.

- in cases where the burden would be traversing a list that would
otherwise _not_ have been traversed, the check is not done.

That is, checks are made that don't change the expression inside the big Oh.
Not a bad rule for a library implementor.
unknown
2005-02-08 00:51:20 UTC
Permalink
The bad news is that F must be allowed to be a "tuple fun" too,
to keep backward compatibility.

Thus the revised code must look like this:

map(F, [H|T]) ->
[F(H)|map(F, T)];
map(F, []) when is_function(F) -> %% andalso is_fun_arity(F) == 1
[];
map({M,F}, []) when is_atom(M), is_atom(F) ->
[].

That should still give the Dialyzer more information about the type.

Everywhere we have an argument that should be a function but might not
always be applied we are going to have the same problem. A fix that adds
an extra clause is going to be a pain.

We need
is_applicable(F, N)
- succeeds or returns true when N is a non-negative integer
and F is such that F(T1,...,TN) would make sense
- fails or returns false otherwise
is_applicable(F)
- succeeds or returns true when there exists an N such that
is_applicable(F, N) would succeed or return true
- fails or returns false otherwise

Then
map(F, [F|T]) -> [F(H)|map(F,T)];
map(F, []) when is_applicable(F, 2) -> [].
unknown
2005-02-08 09:50:00 UTC
Permalink
Seems like good solution to me. It should be easy to implement.

Kostis, would that solution work for Dialyzer?

/Bj?rn

"Richard A. O'Keefe" <ok> writes:
[...]
Post by unknown
We need
is_applicable(F, N)
- succeeds or returns true when N is a non-negative integer
and F is such that F(T1,...,TN) would make sense
- fails or returns false otherwise
is_applicable(F)
- succeeds or returns true when there exists an N such that
is_applicable(F, N) would succeed or return true
- fails or returns false otherwise
Then
map(F, [F|T]) -> [F(H)|map(F,T)];
map(F, []) when is_applicable(F, 2) -> [].
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2005-02-08 10:28:30 UTC
Permalink
Post by unknown
Seems like good solution to me. It should be easy to implement.
Kostis, would that solution work for Dialyzer?
Yes, it will.

However, apparently I am missing the obvious, because I do not see
how this is easier (though I see that it is more elegant) to
implement than what we were essentially discussing yesterday...

The key question is: does "is_applicable({M,F}, 1)" always check that
the M:F/1 is a valid fun (and succeeds accordingly) or not?
Post by unknown
[...]
Post by unknown
We need
is_applicable(F, N)
- succeeds or returns true when N is a non-negative integer
and F is such that F(T1,...,TN) would make sense
- fails or returns false otherwise
is_applicable(F)
- succeeds or returns true when there exists an N such that
is_applicable(F, N) would succeed or return true
- fails or returns false otherwise
Then
map(F, [F|T]) -> [F(H)|map(F,T)];
map(F, []) when is_applicable(F, 2) -> [].
(I am assuming the "2" above is just a typo and should read "1" BTW).

Kostis
unknown
2005-02-08 10:54:15 UTC
Permalink
Kostis Sagonas <kostis> writes:
[...]
Post by unknown
However, apparently I am missing the obvious, because I do not see
how this is easier (though I see that it is more elegant) to
implement than what we were essentially discussing yesterday...
It is essentially the same, just packaged in a much more elegant way.
Post by unknown
The key question is: does "is_applicable({M,F}, 1)" always check that
the M:F/1 is a valid fun (and succeeds accordingly) or not?
No. is_applicable/2 cannot still check whether M:F/A will be possible to
apply or not. is_applicable/2 would have to assume that any tuple containing
two atom elements is a valid tuple fun.

But as I see two advantages using is_applicable/2:

1) When/if we'll finally be able to eliminate tuple funs, we can change
is_applicable/2 to no longer accept tuple funs (as opposed to changing
Erlang code in many places).

2) When Dialyzer sees is_applicable/2, it is a stronger type indication
than just 'foo({M,F}) when is_atom(M), is_atom(F)' which need not be
a tuple fun. At least it should be enough indication for Dialyzer to
assume that F has the same type in both clauses of lists:map/2. That
should allow Dialyzer to generate more warnings than it can today.

If it would be of help to Dialyzer, we might be able to add is_applicable/2
even to R10B-4 (and start using it in library modules such as lists).

On the other hand, I can give no definite time plan for when we'll
be able to forbid tuple funs. Sorry.

/Bj?rn
--
Bj?rn Gustavsson, Erlang/OTP, Ericsson AB
unknown
2005-02-08 12:12:31 UTC
Permalink
Post by unknown
Post by unknown
It is essentially the same, just packaged in a much more elegant way.
Ok, at least now I know we are on the same wavelength.
Post by unknown
Post by unknown
The key question is: does "is_applicable({M,F}, 1)" always check that
the M:F/1 is a valid fun (and succeeds accordingly) or not?
No. is_applicable/2 cannot still check whether M:F/A will be possible to
apply or not. is_applicable/2 would have to assume that any tuple containing
two atom elements is a valid tuple fun.
1) When/if we'll finally be able to eliminate tuple funs, we can change
is_applicable/2 to no longer accept tuple funs (as opposed to changing
Erlang code in many places).
2) When Dialyzer sees is_applicable/2, it is a stronger type indication
than just 'foo({M,F}) when is_atom(M), is_atom(F)' which need not be
a tuple fun. At least it should be enough indication for Dialyzer to
assume that F has the same type in both clauses of lists:map/2. That
should allow Dialyzer to generate more warnings than it can today.
If it would be of help to Dialyzer, we might be able to add is_applicable/2
even to R10B-4 (and start using it in library modules such as lists).
Sounds a reasonable plan. Since is_applicable/2 will, some fine day,
be changed to only accept proper funs in its first argument and till
then will only be doing crippled tests with tuple funs anyway,
let me propose that we call this is_function/2 instead.

So map/2 will read:

map(F, [F|T]) -> [F(H)|map(F,T)];
map(F, []) when is_function(F,1) -> [].

This is more consistent with the current is_function/1 guard (which
is the is_applicable/1 which Richard O'Keefe has described).
(Moreover, you can see the current implementation of is_function/1
as one where the tuple fun case is already gone! :-)

Kostis
unknown
2005-02-09 01:52:02 UTC
Permalink
I proposed
Post by unknown
is_applicable(F, N)
- succeeds or returns true when N is a non-negative integer
and F is such that F(T1,...,TN) would make sense
- fails or returns false otherwise
is_applicable(F)
- succeeds or returns true when there exists an N such that
is_applicable(F, N) would succeed or return true
- fails or returns false otherwise
The words "would make sense" were very carefully chosen.

Kostis Sagonas <kostis> wrote:
However, apparently I am missing the obvious, because I do not see
how this is easier (though I see that it is more elegant) to
implement than what we were essentially discussing yesterday...

It isn't easier to IMPLEMENT but easier to USE.

Remember the context. Someone else proposed

map(F, [H|T]) -> [F(H) | map(F, T)];
map(F, []) when is_function(F) -> [];
map({M,F}, []) when is_atom(M), is_atom(F) -> [].

This is problematic in two ways.
First, when the argument *is* a function it doesn't check as much as
it easily could. (It doesn't check the arity.)

Second, the extra clause to check for {M,F} is such a pain to add
that it probably won't be done consistently throughout the libraries.

is_applicable/[1,2] is meant to address both points:
- if there is a required arity, that can be passed
and used for a more precise test
- it is *MUCH* easier to use than adding an extra clause.

The key question is: does "is_applicable({M,F}, 1)" always check that
the M:F/1 is a valid fun (and succeeds accordingly) or not?

"would make sense" is not the same as "is currently defined".
Like other guards, is_applicable/[1,2] is meant to be a *time-independent*
test which can be checked by *local inspection* of the term in question.

Consider this example:

m:f(X) -> unload module X.

lists:map({m,f}, [m])

At the time the call *starts*, m:f/1 *is* defined.
By the time the call reaches [], m:f/1 is *not* defined any longer.
If we had the stronger time-dependent non-local check we'd get a
run-time error, for a call that actually made sense at the beginning
and has in fact completed without any difficulty.

Closures don't have that problem. Once a closure of arity 1, always
a closure of arity 1. That doesn't actually mean they are safer:

lists:map(fun (X) -> m:f(X) end, [m])

It makes sense to think of {m,f} as short-hand for
"whichever of
fun (X1) -> m:f(X1) end
...
fun (X1,...,X99) -> m:f(X1,...,X999) end
...
is actually required at the time of call."
So it makes sense to treat it has having the same validity as the
corresponding fun.

(I am assuming the "2" above is just a typo and should read "1" BTW).

No, it was a thinko: too much Prolog. It should read "1".
unknown
2005-02-23 08:20:56 UTC
Permalink
Yes, it is used in (older parts of) our software at CellPoint.

/Fredrik
-----Original Message-----
From: owner-erlang-questions
[mailto:owner-erlang-questions]On Behalf Of Luke Gorrie
Sent: den 22 februari 2005 20:40
To: erlang-questions
Subject: Re: Proposed change to libraries
Post by unknown
Unfortunately, it is tricky to check that {M,F} is a valid
fun. The module
Post by unknown
M might not even be loaded.
It has been suggested earlier that we should add
fun M:F/Arity
to the language. Unfortunately, we didn't add it in R10B.
We could add
Post by unknown
it in R11B.
#Fun<M:F/Arity>
so that you can easily see where it's pointing.
I also wonder if people are using {M,F}-funs without fixed arity.
callback(Values, Callback = {M,F}) when list(Values) ->
apply(M, F, Values).
Cheers,
Luke
unknown
2005-02-23 08:30:54 UTC
Permalink
Post by unknown
Yes, it is used in (older parts of) our software at CellPoint.
[...]
Post by unknown
Post by unknown
callback(Values, Callback = {M,F}) when list(Values) ->
apply(M, F, Values).
but if I had thought a bit harder I'd have realised that this is just
an apply/3 and not really related to the tuple-function business.
That would have to be something like

CB = {mod,fun},
if ... -> CB();
... -> CB(Arg)
end

which looks a bit more exotic.

-Luke
unknown
2005-02-23 08:56:58 UTC
Permalink
From: Luke Gorrie
Post by unknown
Yes, it is used in (older parts of) our software at CellPoint.
[...]
Post by unknown
Post by unknown
callback(Values, Callback = {M,F}) when list(Values) ->
apply(M, F, Values).
but if I had thought a bit harder I'd have realised that this is just
an apply/3 and not really related to the tuple-function business.
That would have to be something like
CB = {mod,fun},
if ... -> CB();
... -> CB(Arg)
end
which looks a bit more exotic.
Well, I did the same mistake. But the reply is the same - it is in use (but I'd change it to the fun M:F/A syntax as soon as it's there).

/Fredrik
unknown
2005-09-28 13:37:32 UTC
Permalink
Are the Erlang Workshop proceedings available anywhere online?
Not yet as far as I know, but they will be available at the ACM
Digital Library.
Unfortunately missed workshop despite being held practically at home:(
Another option is to go and ask for or buy a copy at the workshop's
venue in Tallinn. There are other co-located events still going on
till the end of this week.

Best,
Kostis

Continue reading on narkive:
Loading...