[erlang-questions] Package Support/Use

Ulf Wiger ulf@REDACTED
Tue Nov 7 08:50:29 CET 2006


Den 2006-11-07 03:32:18 skrev Richard A. O'Keefe <ok@REDACTED>:

>
> 	   in Traditional Erlang something like this;
> 	
> 	   {ok,FD} = open(Name),
> 	   {ok,Data} = read(FD),
> 	   {ok,PD} = process_data(Data),
> 	
> Again, that is pretty unforgivable Erlang:  if open/1 creates
> a resource which will not be automatically freed.

Incorrect. The file will be closed automatically if the
process dies.

(But if the surrounding call is caught, this will of course
not happen.)


>  As has been
> pointed out on this list before,  just define
>
> 	ok({ok,X}) -> X;
> 	ok(Error) -> signal some kind of error.
>
> and your Traditional Erlang can become
>
> 	ok(process_data(ok(read(ok(open(Name))))))
>
> 	   in BettErl (tm);
> 	
> 	   process_data(read(open(Name))),
> 	
> 	   TradErl is more verbose and binds worthless
>        intermediate variables.
> 	
> Verbose, yes, a little bit.  But it certainly doesn't
> REQUIRE you to "bind worthless intermediate variables",
> that's a straw man.

But in fact, lots of erlang code out there actually looks
like this (and I think it's fair to say that Mats has seen
more Erlang code than most). The style of programming Mats
calls TradErl is also used in examples (without ok/1 wrappers,
which I believe to be rather uncommon) in OTP documentation.

The 1-minute google test gave me the gen_tcp man page:

http://www.erlang.org/doc/doc-5.5.1/lib/kernel-2.11.1/doc/html/gen_tcp.html

where example code looks like:

server() ->
     {ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0},
                                         {active, false}]),
     {ok, Sock} = gen_tcp:accept(LSock),
     {ok, Bin} = do_recv(Sock, []),
     ok = gen_tcp:close(Sock),
     Bin.



> Traditional Erlang, with the ok/1 function, gives you the CHOICE
> of looking at the error result or not, and if you choose not to,
> there are NO intermediate variables unless you want them.
>
> 	   TradErl has poor error reporting.
>
> 	   it will either succeed or fail with "badmatch". not very helpful.
>
> Again, this is only because the example is a straw man.  With the
> ok/1 function, you DON'T 'fail with "badmatch"', you get the full
> information that the function returned.

Again, a correction is in order.
Failing with "badmatch" doesn't mean that you only get 'badmatch'.
You actually get the whole term that didn't match. In the file
examples, you would get e.g. {badmatch, {error, enoent}}, but no
information about what file operation caused it. In order for
your ok/1 wrapper to give you any more than that, you have to
provide it with some additional info. This is perhaps one reason
why they're not used more often.


> So no, The "TradErl" approach
> is IN NO WAY INFERIOR with regard to error reporting.

My experience is that it is. In many cases, the badmatch
errors are sufficient, but too often they're not. This has
the effect of prompting many programmers to write case
statements even when they shouldn't have to. It's difficult
to get them to change, partly because many programmers seem
reluctant to have their program crash.


> The real issue is how exceptional the exceptional condition is and
> whether it is likely that the caller can do anything in an error
> case other than pass it on.

Agreed.

> A regular expression not matching is not exceptional.
> A regular expression having bad syntax IS exceptional.
> Reaching the end of a file *between* characters is not exceptional.
> Reaching the end of a file *within* the UTF-8 byte sequence for a
> character IS exceptional.

Agreed.

>
> For me in the open/read/process example, the really important thing is
>
>     - if the file cannot be opened,
>       is there likely to be anything the immediate caller can
>       do about it?
>       (Probably not:  good case for an exception.)

But instead you get a wrapped error code, whether the reason be that
the file doesn't exist, it exists but you don't have access to it,
or you passed junk input to the function. The latter is a bug, and
will be fixed, if I understood Björn correctly.


>     - if the read fails, is there anything the caller can do about it?
> 	A negative result from read(2) counts as exceptional; there is
> 	probably nothing the program can do about it (except when errno
> 	is EAGAIN, of course).
> 	There is something the caller MUST do, and that is close the file.

In C, but not in Erlang. The caller also has the option of dying,
in which case the file will be closed automatically, as will all
ports held by erlang processes.

In some cases, even an Erlang programmer must take care to clean up
a resource. In the file case, it's obviously when the caller _catches_
an error and continues. To make this easy, the new try construct
has an 'after' clause:

termize_file(Name) ->
     {ok,F} = file:open(Name, [read,binary]),
     try
         {ok,Bin} = file:read(F, 1024*1024),
         binary_to_term(Bin)
     after
         file:close(F)
     end.

Example taken from the Erlang Reference Manual. Ironically,
the function contains two TradErl assertions that can both
fail with very similar 'badmatch' exceptions. What it does
show, however, is how you can signal with 'try ... after'
that a sequence _can_ fail, and that the file (if it were
opened) will not be left opened.

As you use this construct in your later example, I'm
aware that you know about it.



> What does the code *really* look like in these two cases?
>
>       % tagged results
>
> 	Channel = ok(open(Name)),
> 	Outcome = read(Channel),
> 	close(Channel),
> 	case Outcome
> 	 of  {ok,Data} -> ok(process_data(Data))
> 	  ;  _         -> ok(Outcome)
> 	end
>
>       % exceptions
>
> 	Channel = open(Name),
> 	try
> 	    Data = read(Channel),
> 	after
> 	    close(Channel)
> 	end,
> 	process_data(Data)
>
> Funny how the difference becomes less clear-cut when you make the
> example more realistic, isn't it?

But in the "tagged results" example above, you don't
close the Channel if read/1 crashes, right?


> One way to think about it is this:
>
>     if this outcome occurs, would I be happy for the entire calling
>     process to drop dead (with a suitable error report)?
>
> If the answer is "yes", then an exception is probably the right thing.

Absolutely. This is why timer:send_after/2 returning {error, Reason}
is my favourite example of bad use of tagged return values. I've never
been able to figure out what fruitful action the caller should take
when the system responds, "sorry, you can't start a timer at this
point in time."  (:

BR,
Ulf W
-- 
Ulf Wiger



More information about the erlang-questions mailing list