[erlang-questions] checking for unbound?

Alain O'Dea alain.odea@REDACTED
Sat Nov 20 17:28:28 CET 2010


On Saturday, November 20, 2010, Jesper Louis Andersen
<jesper.louis.andersen@REDACTED> wrote:
> On Sat, Nov 20, 2010 at 3:18 PM, Andy Kriger <andy.kriger@REDACTED> wrote:
>> Is there a way to test if a variable is unbound?
>
> I am a bit hesitant to guess at what you mean by "unbound variable".
> Variables which are free (In the lambda-calculus terminology of
> "free") lead to compilation errors since terms ultimately have to be
> closed. Thus referral to a variable not existing in the scope or
> failing to use a variable in a scope results in an error.
>
>> I thought I could do something clever like...
>> [E|EL] = mnesia:dirty_index_read(myTable, value, indexField),
>> if <E is unbound> -> do something; % nothing was returned
>>  length(EL) > 0 -> do something; % more than 1 item was returned
>>  <E matches pattern> -> do something; % a match was returned
>>  true -> do something % whatever is left
>
> We can look up mnesia:dirty_index_read/3 via 'erl -man mnesia', and
> see it returns a RecordList. Thus, pattern matching to the rescue:
>
> case mnesia:dirty_index_read(T, v, i) of
>   [] -> do something; % Nothing was returned
>   [Obj] -> do something ; % Exactly one object was returned
>   RL when is_list(RL) -> do something; % We got a whole list of things
> end,
>
> notice how the case match is exhaustive. It matches all possible
> outcomes (and strictly speaking we could have been exhaustive without
> the [Obj] match). A rule of Erlang is that any expression will
> *always* return a value. There is no equivalent to C's "void" and no
> "null" as well. Any identifier is always bound to a value or the
> program is wrong. This concept is very powerful: you don't have to
> fear the null pointer exception and you don't have to check for null
> unless the program returns an atom playing the role of the concept
> "null".
>
> It is quite often the case that functional languages choose their
> expressions in this way. In fact, it would be hard to do otherwise,
> since it would remove you from the lambda-calculus basis.
>
>> But I don't see a way to make the first test.
>
> Pattern matching is the key to the puzzle. You let a specific value
> play the role of being "unbound". In the above case, the role is
> played by the empty list, [], in other cases it is played by an atom
> -- usually 'undefined', 'none', 'aborted' -- and in some cases, it is
> played by a complete term: '{error, Reason}', '{parse_error,
> PartialParse}' and so on. An example of an atom, 'undefined', playing
> the role is when asking the application module for configuration
> options:
>
> case application:get_env(MyModule, profiling) of
>   undefined -> false
>   {ok, Bool} when is_boolean(Bool) -> Bool
> end,
>
> Many imperative languages would have chosen to write a program along
> the lines (somewhat Go-ish syntax),
>
> V := application.GetEnvBool(MYMODULE, "profiling")
> if (V == nil) {
>   return false
> } else {
>   return V
> }
>
> because any identifier in these languages referencing an object is
> essentially a pointer which can be nil/null. Personally, I find this
> ugly, and cumbersome to write and I much prefer the functional
> variant, of which Erlang is an example.
>

Short version:

[E|EL] = mnesia:dirty_index_read(myTable, value, indexField) is a
pattern match expression.  All expressions in Erlang have values
(dirty_index_read must have some value, unbound is not possible).  If
you get something other than a list with one or more elements you'll
get a crash with {error, bad_match} as the cause.

Jesper's case expression is a possible solution.

My style preference is to use multiple functions and pattern-matching:

    %...
    handle_read(mnesia:dirty_index_read(myTable, value, indexField)),
    %...

handle_read([]) ->
     % handle no items

handle_read([E]) ->
     % handle one item

handle_read([_|_] = EL) ->
     % handle many items


More information about the erlang-questions mailing list