Small poll

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

Small poll

Richard A. O'Keefe
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.



Reply | Threaded
Open this post in threaded view
|

Small poll

Bengt Kleberg-4
Richard A. O'Keefe wrote:
> 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?

>
> 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


Reply | Threaded
Open this post in threaded view
|

Small poll

Joachim Durchholz
In reply to this post by Richard A. O'Keefe
Richard A. O'Keefe wrote:
>
> 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.)

 > 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.

> 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).

> 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?

> 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



Reply | Threaded
Open this post in threaded view
|

Small poll

Chris Pressey
In reply to this post by Bengt Kleberg-4
On Wed, 17 Dec 2003 10:09:05 +0100
Bengt Kleberg <Bengt.Kleberg> wrote:

> 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


Reply | Threaded
Open this post in threaded view
|

Small poll

Pascal Brisset-3
Chris Pressey writes:
 >   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



Reply | Threaded
Open this post in threaded view
|

Small poll

Chris Pressey
On Wed, 17 Dec 2003 21:17:35 +0100
Pascal Brisset <pascal.brisset-ml> wrote:

> Chris Pressey writes:
>  >   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.


Reply | Threaded
Open this post in threaded view
|

Small poll

Bengt Kleberg-4
In reply to this post by Chris Pressey
Chris Pressey wrote:
...deleted

> 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

> 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


Reply | Threaded
Open this post in threaded view
|

Small poll

Chris Pressey
On Thu, 18 Dec 2003 10:29:06 +0100
Bengt Kleberg <Bengt.Kleberg> wrote:

> Chris Pressey wrote:
> > 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



Reply | Threaded
Open this post in threaded view
|

Small poll

Michał Ptaszek


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
Chris Pressey <cpressey> wrote:

> On Thu, 18 Dec 2003 10:29:06 +0100
> Bengt Kleberg <Bengt.Kleberg> wrote:
>
> > Chris Pressey wrote:
> > > 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
>
>


--
"The Tao of Programming
 flows far away
 and returns
 on the wind of morning."



Reply | Threaded
Open this post in threaded view
|

Small poll

Joachim Durchholz
In reply to this post by Chris Pressey
Chris Pressey wrote:

> Bengt Kleberg <Bengt.Kleberg> wrote:
>
>> Chris Pressey wrote:
>>
>> 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



Reply | Threaded
Open this post in threaded view
|

Small poll

Bengt Kleberg-4
In reply to this post by Chris Pressey
Chris Pressey wrote:
...deleted
> 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.


> 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