length() in guards
Richard A. O'Keefe
ok@REDACTED
Mon Sep 29 05:13:32 CEST 2003
One of the perennial questions about Erlang is "can't we generalise
guards to allow any expression?" The standard response is that the
guard tests and expressions are supposed to have certain properties.
And the standard retort to that is that length/1 does't have them.
A tiny restriction would tame length/1 in guards.
The restriction is
"a call to length/1 may appear in a guard only as an entire
operand of a comparison (==, /=, <, >=, >, <=, =:=, -/=)
in which the other operand is not a call to length/1".
So
length(X) == 4
would be allowed, but
length(X)*2 == 8
would not be allowed.
This helps because computing length(X) requires time proportional
to the length of X, while computing length(X) <relop> N, where N
is an integer, requires time O(max(1,N)). For example, the guard
test
length(X) /= 2
can be evaluated in O(1) time, no matter how long X is.
Poking around in the Erlang sources, I've found that the majority
of uses of length/1 in a guard are equivalent to
length(<var>) <relop> (<const> | <var> | size(<var>))
There are some exceptions.
There's a use of
when length(<var1>) > length(<var2>)
that not only could be expressed without doing that, but would be
O(N) instead of O(N**2) if so expressed.
There's a use of
when length(<var1>) == length(<var2>)
guarding a call to a function so that 'false' can be returned if the
lists aren't the same length, BUT that function would return 'false'
anyway in that case, AND <var1> is actually a loop invariant, so the
module doesn't really need to do this and would be faster if it didn't.
There's a function
f(E, F, [], false) when length(E) + length(F) == 8 ->
list_to_tuple(reverse(E) ++ reverse(F));
f(E, F, [D,C,B,A], false) when length(E) + length(F) == 6 ->
list_to_tuple(reverse(E) ++ reverse(F) ++ [A*256+B, C*256+D]);
f(E, F, [], true) when length(E)+length(F) =< 8 ->
list_to_tuple(reverse(E) ++ zero(8-(length(E)+length(F))) ++ reverse(F));
f(E, F, [D,C,B,A], true) when length(E)+length(F) =< 6 ->
list_to_tuple(reverse(E) ++ zero(6-(length(E)+length(F))) ++ reverse(F) ++
[A*256+B, C*256+D]).
which could be written as
f(E, F, X, B) ->
list_to_tuple(f(E, F, X, B, length(E)+length(F))).
f(E, F, [], false, 8) ->
reverse(E) ++ reverse(F);
f(E, F, [D,C,B,A], false, 6) ->
reverse(E) ++ reverse(F) ++ [A*256+B,C*256+D];
f(E, F, [], true, N) when N =< 8 ->
reverse(E) ++ zero(8-N) ++ reverse(F);
f(E, F, [D,C,B,A], true, N) when N =< 6 ->
reverse(E) ++ zero(6-N) ++ reverse(F) ++ [A*256+B,C*256+D].
There's a
when <var1> + length(<var2>) <relop> <var3>
that could be
when length(<var2>) <relop> <var3> - <var1>
There's a function
f(X, Y) ->
case lists:prefix(X, Y#field) of
true when length(Y#field) > length(X) ->
g(X, Y);
false ->
above
end.
which could simply be
f(X, Y) ->
case lists:proper_prefix(X, Y#field) of
true -> g(X, Y);
false -> above
end.
if only the lists module included
proper_prefix([X|Prefix], [X|List]) ->
proper_prefix(Prefix, List);
proper_prefix([], [_|_]) -> true;
proper_prefix(_, _) -> false.
which it should anyway. When we look at g/2, we find
g(X, Y) when length(Y#field) == length(X) + 1 ->
<stuff>;
g(X, Y) when length(Y#field) > length(X) + 1 ->
<other stuff>.
So this pair of functions could be
f(X, Y) ->
Delta = length(Y#field) - length(X),
case lists:prefix(X, Y#field) of
true when Delta > 0 ->
g(X, Y, Delta);
false ->
above
end.
g(X, Y, Delta) when Delta == 1 ->
<stuff>;
g(X, Y, Delta) when Delta > 1 ->
<other stuff>.
There's also a function
f(X, Y) when length(X) == length(Y) ->
<stuff 1>;
f(X, Y) when length(X) > length(Y) ->
<stuff 2>;
f(X, Y) when length(X) < length(Y) ->
<stuff 3>.
This is obviously inefficient; it could so easily have been
f(X, Y) ->
Delta is length(X) - length(Y),
if Delta < 0 -> <stuff 3>;
Delta > 0 -> <stuff 2>;
Delta ==0 -> <stuff 1>
end.
So everywhere I look, either the guards already conform to this
restriction, or the code would be more efficient if it did conform,
and could be at least as clear.
What do people think?
More information about the erlang-questions
mailing list