[erlang-questions] Floating guard sequences

Richard O'Keefe ok@REDACTED
Fri Feb 20 00:53:52 CET 2009


On 20 Feb 2009, at 9:22 am, Zvi wrote:
>
>
> fess-3 wrote:
>>
>> ...
>> floating guards on the LHS of a match seem like they could make
>> code much more concise and readable, and erlang-like [ to me ]
>>
>>
>>   { X when is_integer(X), [ "x" | Rest ] } = string:to_integer(Str),
>>   { Y when is_integer(Y), _ } = string:to_integer(Rest),
>>
>
> much more readable, if we can marry patterns with guards:
>
>   { X = int(), [ "x" | Rest ] } = string:to_integer(Str),
>   { Y = int(), _ } = string:to_integer(Rest),
>
> where int() is patten matching any integer value.

int() would be a function call, which doesn't make much sense
in a guard.

This is a rather interesting example, because
  - string:to_integer/1 and string:to_float/1
    ARE in the on-line documentation
  - they ARE accepted by 'erl'
  - but they are NOT in lib/stdlib/src/string.erl
They both return either {Number,Tail} or {error,Cause}.

I have to say that this interface is brain-dead.
When a function can return one of several outcomes,
it should be possible to tell one from another by a simple
pattern match.  A *good* result would be
	{ok,Number,Tail}
or	{error,Cause}

The solution is not to bloat the pattern sublanguage beyond
all recognition, but to fix the broken interface.

better_to_integer(String) ->
     case string:to_integer(String)
       of {error,Cause} -> {error,Cause}
        ; {Number,Tail} -> {ok, Number,Tail}
     end.

better_to_float(String) ->
     case string:to_float(String)
       of {error,Cause} -> {error,Cause}
        ; {Number,Tail} -> {ok,Number,Tail}
     end.

and then to write a plain

	{ok,X,[$x|Rest]} = better_to_integer(Str),
	{ok,Y,_        } = better_to_integer(Rest)

Testing whether X or Y is an integer ASKS THE WRONG QUESTION.
A better question is "is this not the atom 'error'"?
But the best question is the one we really want: "is this
conversion ok?"  And fixing the interface lets us ask very
simply what we really mean.

There's a common pattern I'm seeing in discussions like this.
There is some ugly code, but people accept the *cause* of the
ugliness and just want to put a glossy coat of paint over
the *symptoms*.

It's a basic principle of good data structure design in Prolog,
which has carried over without change to Erlang, that when a
data structure could be one of several alternatives, you
should be able to recognise each of the alternatives with a
single *positive* pattern match.  (That is, nothing should
be recognised by virtue of not being something else.)  When I
reimplemented the internal data structures of the Quintus
Prolog compiler to follow this principle, not only was the
code easier to read, the compiler ran 20% faster.

> Or, if X and Y unused, like in the example above:
>
>   { int(), [ "x" | Rest ] } = string:to_integer(Str),
>   { int(), _ } = string:to_integer(Rest),

It's *still* better to write

     {ok,_,[$x|Rest]} = better_to_integer(Str),
     {ok,_,_        } = better_to_integer(Rest)

By the way, the pattern ["x"|Rest] should be
[$x|Rest] as I have it, or "x"++Rest.





More information about the erlang-questions mailing list