Good practice

Richard Carlsson richardc@REDACTED
Mon Jul 21 11:42:24 CEST 2003


A bit late (hey, it's summer), but I thought I should try to give an
explanation of this behaviour.

On Fri, 18 Jul 2003, Chris Pressey wrote:

> On Sun, 8 Jun 2003 17:28:01 -0400 (EDT)
> erlang@REDACTED wrote:
>
> > bash$ erl
> > Erlang (JAM) emulator version 47.4.1
> >
> > Eshell V47.4.1  (abort with ^G)
> > 1> c(test1file).
> > ./test1file.erl:172: Warning: variable 'Response' exported from
> > ['case']{ok,test1file}
> > 2>

Wow! JAM version 47.4.1! This is an ancient system. (On the other hand,
that particular compiler warning was not changed until BEAM R7 or R8, so
a lot of other people may also see this warning still.)

> > From the file in question:
> >
> >       case checkvalid(Funcref, Correctlist) of
> >         false ->
> >           Response = false;
> >         Other ->
> >           Response = invoker(Other, Otherargs)
> >       end,
> >       Requester ! Response,
>
> I've never actually understood why this isn't a full-fledged error.
>
> Is there a good reason that bindings are allowed to 'leak' out of a
> case..end?

It is the straightforward extension from the current idea that bindings
'leak' out of a single expression, so you can use the binding later on,
for example:
		X = f(Y),
		...
		g(X)

Now you might say "but that's an *assignment* - it's different!"
But it's not. "X = f(Y)" is just an expression which happens to
contain a variable binding, which is "exported" from the expression
and becomes avaliable in the expressions following the ','.

So, logically, if an expression (like a 'case') contains alternative
branches, the exported bindings from that expression should be those
that are exported from each of the branches. (Those that could be
undefined in one of the branches are "unsafe", and it *is* a compile
time error to use an unsafe variable.)

The code above should be a perfect illustration of a "good" use of this
form of binding. Simple, straightforward, readable.

However, it turned out that in certain programs, this kind of binding
tended to introduce errors. Sometimes, you introduce a variable such as
'X' as a temporary variable in a branch of a 'case' (for instance in the
clause pattern), and you only intend to use it "locally", but if you use
the same name 'X' in *all* the branches, the variable 'X' is suddenly
legally exported to the following expressions. What happens next is of
course that further below, you use the "local" name 'X' *again*, without
realizing that it is already bound to some value. The result is that the
later occurrence of 'X' does not become a binding, but a matching, and
most likely, you get a runtime 'badmatch' error.

The quick fix for this was that the "variable exported" warning was
added to the compiler (in the old JAM days). But this made *all* uses of
such exported variables trigger a warning. It fixed the problem, but was
annoying, since you could no longer use this actual *feature* of the
language without getting compiler warnings all over the place.

Eventually, I got tired of this and submitted a patch to erl_lint that
did a more fine-grained check, so that only repeated occurrences in
patterns cause warnings. Thus, the code in the above example does not
cause warnings today - there is no reason for a warning there. But code
like the following:

	case ... of
	   {foo, X} -> ...;
	   {bar, X} -> ...
	end,
	...
	case
	   {fred, X} -> ...;
	   {barney, X} -> ...
	end

causes warnings at the later uses of 'X', because since they occur in a
pattern, it is unlikely that the author really means "the same X that
was bound above". (It is still just a warning, though, in case you
really want to write code like that.)

In short: exporting variables from 'case' expressions (and similar) is
OK if you just *use* the variables afterwards, but if you put them in
patterns you will get warnings.

> My language designer's intuition is telling me that the scoping rules
> for case..end should be more like those for fun..end.

Erlang's variable scoping rules make it sufficiently different to
confuse any language designer. The hope is still, I think, that the
actual Erlang programmers are helped by them rather than confused. It is
after all pretty straightforward to bind variables in Erlang - you don't
have to think too hard about writing 'let' statements. There is of
course a tradeoff between easy introduction of variables and simple
scoping rules. I don't know any other language that has walked this path
as far as Erlang. You have to ask yourself as a user whether it's good
or bad.

	/Richard


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



More information about the erlang-questions mailing list