I keep wanting to have python-like boolean expression evaluation, in which a value of nil, 0, an empty list or an empty tuple are considered to be false. It would be great to have a consise way of saying "the value of this expression is expr1 if expr1 evaluates to a non-false value, or expr2 otherwise".
In python, you would say "X = foo() or bar()", which would evaluate foo(), and if that evaluates to one of None,False,0,[],(),{}, then evaluate bar()
I cant for the life of me figure out what the most concise way of stating that is in erlang.
perhaps X = if (T1=foo()) =/= [] -> T1, false -> bar() end
it would nice to be able to say something like X = foo() otherwise bar().
> I keep wanting to have python-like boolean expression evaluation, in which > a value of nil, 0, an empty list or an empty tuple are considered to be > false. > It would be great to have a consise way of saying "the value of this > expression is expr1 if expr1 evaluates to a non-false value, or expr2 > otherwise".
> In python, you would say "X = foo() or bar()", which would evaluate foo(), > and if that evaluates to one of None,False,0,[],(),{}, then evaluate bar()
> I cant for the life of me figure out what the most concise way of stating > that is in erlang.
> perhaps > X = if (T1=foo()) =/= [] -> T1, false -> bar() end
> it would nice to be able to say something like > X = foo() otherwise bar().
> On Sun, Nov 16, 2008 at 4:42 AM, mats cronqvist <[1]ma...@kreditor.se> > wrote:
> strangely, this also works; > case foo() of > [] -> X = bar(); > X -> ok > end > as long as X is bound in each clause, you're golden. > this might qualify as a gotcha. > mats
damien morton wrote: > I keep wanting to have python-like boolean expression evaluation, in > which a value of nil, 0, an empty list or an empty tuple are considered > to be false.
> It would be great to have a consise way of saying "the value of this > expression is expr1 if expr1 evaluates to a non-false value, or expr2 > otherwise".
> In python, you would say "X = foo() or bar()", which would evaluate > foo(), and if that evaluates to one of None,False,0,[],(),{}, then > evaluate bar()
Well, andalso/orelse (as someone suggested) don't work since they require that expr1 and expr2 evaluate to booleans and nothing else.
> I cant for the life of me figure out what the most concise way of > stating that is in erlang.
> perhaps > X = if (T1=foo()) =/= [] -> T1, false -> bar() end
> it would nice to be able to say something like > X = foo() otherwise bar().
X = case foo() of [] -> bar(); X1 -> X1 end
If you need to check for other values as well, replace '[] ->' with 'X when X =:= [] ; X =:= 0 ; ... ->'
But I've always felt that this feature of Python/Perl/... boils down to sloppy programming style. It basically means that the caller hopes that the "empty or failure" case is signalled by one of the values reconized as pseudo-booleans by the language (the programmer might not actually know the exact interface of the called function, but guessed that this would work), and the resulting code says nothing to the reader about the actual set of return values. Furthermore, the code might do the wrong thing if the function tries to return e.g. '0' or '{}' on success (as opposed to False or None or whatever it usually uses for failure). It simply makes the code a lot less tight than it ought to be. And then, you still can't use the same idiom on abstract data types to treat e.g. an empty set as "false".
On Sun, Nov 16, 2008 at 1:48 AM, Richard Carlsson <richa...@it.uu.se> wrote: > damien morton wrote:
>> Still, what strikes me about the erlang libraries is the tremendous >> variety of techniques used to signal the return of a value or not.
>> Sometimes nil/Value, sometimes false/Value, sometimes []/[Value], >> sometimes false/{value,Value}
> I've tried to promote {value,X}/none as a standard maybe-type, but it's > hard to do much about all the existing code out there.
> For a function that can return 0 or 1 answers, I personally like the >> []/[Value] approach.
> []/[X] is ok for "searches" as in mnesia, where in some cases you can > get several elements as a result, but as a general mechanism it always > makes a reader think "so, can I ever get more than one value here?", so > it's not good for clarity.
Would {}/{X} be more clear for 0 or 1 answers, and []/[...] for 0 or more answers?
Not too keen on the none/{value,Value} approach - it feels irregular somehow.
You are evaluating these in the shell. In the first case X is bound to aaa, which means that when you evaluate the second case X is already bound. Which means that there is no matching clause. If you do:
1> case [] of [] -> X = aaa; X -> ok end. aaa 2> case bbb of [] -> Y = aaa; Y -> ok end. ok 3> {X,Y}. {aaa,bbb}
X is bound to aaa in the first line you enter in the shell, so your second line is really "case bbb of [] -> aaa = aaa; aaa -> ok end.".
You can unbind/forget X in the shell with f() or f(X), see help().
1> case [] of [] -> X = aaa; X -> ok end. aaa 2> X. aaa 3> case bbb of [] -> X = aaa; X -> ok end. ** exception error: no case clause matching bbb 4> X. aaa 5> f(X). ok 6> X. * 1: variable 'X' is unbound 7> case bbb of [] -> X = aaa; X -> ok end. ok 8> X. bbb
---------- Forwarded message ---------- From: damien morton <dmor...@bitfurnace.com> Date: Sun, Nov 16, 2008 at 1:21 AM Subject: Re: [erlang-questions] conditional expressions To: Richard Carlsson <richa...@it.uu.se>
On Sat, Nov 15, 2008 at 10:49 PM, Richard Carlsson <richa...@it.uu.se>wrote:
> damien morton wrote:
>> ...
> I cant for the life of me figure out what the most concise way of stating >> that is in erlang.
>> perhaps >> X = if (T1=foo()) =/= [] -> T1, false -> bar() end
>> it would nice to be able to say something like >> X = foo() otherwise bar().
> X = case foo() of > [] -> bar(); > X1 -> X1 > end
> If you need to check for other values as well, replace '[] ->' with > 'X when X =:= [] ; X =:= 0 ; ... ->'
Ok, that works and is reasonably concise.
> But I've always felt that this feature of Python/Perl/... boils down > to sloppy programming style. It basically means that the caller hopes > that the "empty or failure" case is signalled by one of the values > reconized as pseudo-booleans by the language (the programmer might > not actually know the exact interface of the called function, but > guessed that this would work), and the resulting code says nothing to > the reader about the actual set of return values. Furthermore, the > code might do the wrong thing if the function tries to return e.g. '0' > or '{}' on success (as opposed to False or None or whatever it usually > uses for failure). It simply makes the code a lot less tight than it > ought to be. And then, you still can't use the same idiom on abstract > data types to treat e.g. an empty set as "false".
Well, Python does have a way of determining if an abstract data type is considered true or false - there's a method the ADT can implement for that.
Still, what strikes me about the erlang libraries is the tremendous variety of techniques used to signal the return of a value or not.
Sometimes nil/Value, sometimes false/Value, sometimes []/[Value], sometimes false/{value,Value}
For a function that can return 0 or 1 answers, I personally like the []/[Value] approach.
> ---------- Forwarded message ---------- > From: damien morton <dmor...@bitfurnace.com> > Date: Sun, Nov 16, 2008 at 1:21 AM > Subject: Re: [erlang-questions] conditional expressions > To: Richard Carlsson <richa...@it.uu.se>
> On Sat, Nov 15, 2008 at 10:49 PM, Richard Carlsson <richa...@it.uu.se>wrote:
>> damien morton wrote:
>>> ...
>> I cant for the life of me figure out what the most concise way of stating >>> that is in erlang.
>>> perhaps >>> X = if (T1=foo()) =/= [] -> T1, false -> bar() end
>>> it would nice to be able to say something like >>> X = foo() otherwise bar().
>> X = case foo() of >> [] -> bar(); >> X1 -> X1 >> end
>> If you need to check for other values as well, replace '[] ->' with >> 'X when X =:= [] ; X =:= 0 ; ... ->'
> Ok, that works and is reasonably concise.
>> But I've always felt that this feature of Python/Perl/... boils down >> to sloppy programming style. It basically means that the caller hopes >> that the "empty or failure" case is signalled by one of the values >> reconized as pseudo-booleans by the language (the programmer might >> not actually know the exact interface of the called function, but >> guessed that this would work), and the resulting code says nothing to >> the reader about the actual set of return values. Furthermore, the >> code might do the wrong thing if the function tries to return e.g. '0' >> or '{}' on success (as opposed to False or None or whatever it usually >> uses for failure). It simply makes the code a lot less tight than it >> ought to be. And then, you still can't use the same idiom on abstract >> data types to treat e.g. an empty set as "false".
> Well, Python does have a way of determining if an abstract data type is > considered true or false - there's a method the ADT can implement for that.
> Still, what strikes me about the erlang libraries is the tremendous variety > of techniques used to signal the return of a value or not.
> Sometimes nil/Value, sometimes false/Value, sometimes []/[Value], sometimes > false/{value,Value}
> For a function that can return 0 or 1 answers, I personally like the > []/[Value] approach.
It happen because there is not pattern matching in python, perl, ruby, ... but you don't need it in erlang since there is pattern matching in erlang.
> I keep wanting to have python-like boolean expression evaluation, in > which a value of nil, 0, an empty list or an empty tuple are > considered to be false.
You could do it via a macro that expanded ?PY_OR(e1, e2) to case py_val(e1) of {X} -> X ; false -> e2 end
where py_val(undef) -> false; % Closer match for Undef than nil is py_val(0) -> false; py_val(0.0) -> false; py_val([]) -> false; py_val({}) -> false; py_val(false) -> false; py_val(X) -> {X}.
However, this really is not very Erlang-like. (For one thing, it's not very Dialyser-friendly.)
Lisp had a single value used to represent logical falsehood (NIL), the empty list ('() => NIL), and a three-letter atom ('NIL => NIL). Scheme found it very useful to separate them (#f, '(), and 'NIL are all different in Scheme). Erlang is closer to Scheme here.
It may be that you are trying to write Python in Erlang, which isn't the best way to use Erlang. It may also be that you don't have (m)any cases where ALL of the 'false' values listed above are actually possible, just many cases where one of them is possible and you would like to use the same operator for all of them. Well, 'case' is definitely the construct you want.
On 15 Nov 2008, at 2:13 pm, Roger Critchlow wrote:
> I think you're looking for the andalso and orelse operators. You say:
> X = foo() orelse bar(),
> and bar() only gets called if the value of foo() is false.
However there is a strong restriction in Erlang that does not exist in Lisp, Scheme, Smalltalk, &c and does not exist in Python. False or 42 => 42 in Python 42 or False => 42 in Python 1 == 2 orelse 42 => ERROR! in Erlang 42 orelse 1 == 2 => ERROR! in Erlang
In Erlang, both operands of an andalso or orelse operator must return 'true' or 'false'. I strongly suspect that the OP wants a non-empty list or tuple to count as true, rather than as an error.
Richard O'Keefe wrote: > On 15 Nov 2008, at 2:13 pm, Roger Critchlow wrote:
>> I think you're looking for the andalso and orelse operators. You say:
>> X = foo() orelse bar(),
>> and bar() only gets called if the value of foo() is false.
> However there is a strong restriction in Erlang that does not > exist in Lisp, Scheme, Smalltalk, &c and does not exist in > Python. > False or 42 => 42 in Python > 42 or False => 42 in Python > 1 == 2 orelse 42 => ERROR! in Erlang > 42 orelse 1 == 2 => ERROR! in Erlang
> In Erlang, both operands of an andalso or orelse operator > must return 'true' or 'false'. I strongly suspect that the > OP wants a non-empty list or tuple to count as true, rather > than as an error.
On 18 Nov 2008, at 4:38 pm, Serge Aleynikov wrote:
> Your 'must' statement is not entirely accurate.
Was there any doubt about what I meant? Let's try again: both 'andalso' and 'orelse' in Erlang have the following properties: the first operand must either return 'true' or 'false' or raise an exception; the second operand, if executed, must either return 'true' or 'false' or raise an exception; the Dialyzer will complain if either operand looks as if it will return something other than 'false' or 'true'; and above all: you CANNOT use them to get the same effect as Lisp's (OR - -) and (AND - -) or Python's similar operators, which allow a normal result from the second operand to be anything at all.
I have in fact complained about this, not because I particularly want non-Boolean results from these operators, but because the code that's inserted to check makes them non-tail-recursive.
> > On 18 Nov 2008, at 4:38 pm, Serge Aleynikov wrote: > > > Your 'must' statement is not entirely accurate. > > Was there any doubt about what I meant? > Let's try again: > both 'andalso' and 'orelse' in Erlang have the > following properties: > the first operand must either return 'true' or 'false' > or raise an exception; > the second operand, if executed, must either return > 'true' or 'false' or raise an exception; > the Dialyzer will complain if either operand looks > as if it will return something other than 'false' or 'true'; > and above all: > you CANNOT use them to get the same effect as > Lisp's (OR - -) and (AND - -) or Python's similar > operators, which allow a normal result from the > second operand to be anything at all. > > I have in fact complained about this, not because I particularly > want non-Boolean results from these operators, but because the > code that's inserted to check makes them non-tail-recursive.
Indeed, the failure of these operators to be properly tail-recursive makes them utterly useless, and dangerous.
The HiPE compiler once had a performance bug in its register allocator because some code used an 'andalso' to control a tail-recursive computation. Needless to say I've since placed a ban on andalso/orelse in the HiPE code I maintain. _______________________________________________ erlang-questions mailing list erlang-questi...@erlang.org http://www.erlang.org/mailman/listinfo/erlang-questions
> -----Original Message----- > From: erlang-questions-boun...@erlang.org [mailto:erlang-questions- > boun...@erlang.org] On Behalf Of Mikael Pettersson > Sent: Wednesday, November 19, 2008 03:09 > To: Richard O'Keefe > Cc: erlang-questi...@erlang.org > Subject: Re: [erlang-questions] conditional expressions
> Richard O'Keefe writes:
> > On 18 Nov 2008, at 4:38 pm, Serge Aleynikov wrote:
> > > Your 'must' statement is not entirely accurate.
> > Was there any doubt about what I meant? > > Let's try again: > > both 'andalso' and 'orelse' in Erlang have the > > following properties: > > the first operand must either return 'true' or 'false' > > or raise an exception; > > the second operand, if executed, must either return > > 'true' or 'false' or raise an exception; > > the Dialyzer will complain if either operand looks > > as if it will return something other than 'false' or 'true'; > > and above all: > > you CANNOT use them to get the same effect as > > Lisp's (OR - -) and (AND - -) or Python's similar > > operators, which allow a normal result from the > > second operand to be anything at all.
> > I have in fact complained about this, not because I particularly > > want non-Boolean results from these operators, but because the > > code that's inserted to check makes them non-tail-recursive.
> Indeed, the failure of these operators to be properly > tail-recursive makes them utterly useless, and dangerous.
> The HiPE compiler once had a performance bug in its > register allocator because some code used an 'andalso' > to control a tail-recursive computation. Needless to say > I've since placed a ban on andalso/orelse in the HiPE code > I maintain. > _______________________________________________ > erlang-questions mailing list > erlang-questi...@erlang.org > http://www.erlang.org/mailman/listinfo/erlang-questions
David Mercer wrote: > Can someone please explain the tail recursion problem with andalso? Thanks.
It checks that the return value of the right hand side is a boolean, so it must wait for the evaluation to return; it can't just do a tail call if the left hand side evaluates to true. (The OTP folks thought that error checking was more important than tail recursion for these operators.)
On Wed, Nov 19, 2008 at 4:28 PM, Richard Carlsson <richa...@it.uu.se> wrote: > David Mercer wrote: >> Can someone please explain the tail recursion problem with andalso? Thanks.
> It checks that the return value of the right hand side is a boolean, > so it must wait for the evaluation to return; it can't just do a tail > call if the left hand side evaluates to true. (The OTP folks thought > that error checking was more important than tail recursion for these > operators.)
Maybe we should reconsider that decision for R13.
I agree with Mikael that andalso/orelse are both useless and dangerous.
Richard Carlsson wrote: > David Mercer wrote: > > Can someone please explain the tail recursion problem with andalso? > Thanks.
> It checks that the return value of the right hand side is a boolean, > so it must wait for the evaluation to return; it can't just do a tail > call if the left hand side evaluates to true. (The OTP folks thought > that error checking was more important than tail recursion for these > operators.)
Thanks for the reply, but I still do not understand. If the first operand evaluates to true the second operand must necessarily be checked, since they both have to be true in order for the expression to evaluate to true. What am I missing (or mißing :-) )?
David Mercer wrote: > Richard Carlsson wrote: >> David Mercer wrote: >>> Can someone please explain the tail recursion problem with andalso? >> Thanks.
>> It checks that the return value of the right hand side is a boolean, >> so it must wait for the evaluation to return; it can't just do a tail >> call if the left hand side evaluates to true. (The OTP folks thought >> that error checking was more important than tail recursion for these >> operators.)
> Thanks for the reply, but I still do not understand. If the first operand > evaluates to true the second operand must necessarily be checked, since they > both have to be true in order for the expression to evaluate to true. What > am I missing (or mißing :-) )?
If the LHS is true, then it all hangs on the RHS, so you can A) call it with a tail call (if the result of the andalso will be returned as the result of the current function) and hope that it evaluates to a boolean as it ought to, or B) call it, get the return value, check that it is indeed a boolean, and either return it as it is or throw an exception if it is not a boolean.
The current implementation does B, which makes it less suitable for this kind of thing:
all_true([H|T]) -> H andalso all_true(T); all_true([]) -> true.
which would be a simple traversal if the implementation was A, but as it is, we have to return down the stack and check that we are getting neat little booleans all the way. (Yes, type analysis could sometimes save the day, but far from always.)
On the other hand, B prevents uses such as this:
maybe_goodbye(X) -> X andalso io:format("adieu").
(which returns false if X is false, and otherwise prints a message and returns ok).
The recursive call here is NOT a tail call, because it is semantically the equivalent of
all(_, []) -> true; all(F, [X|Xs]) -> case F(X) of false -> false ; true -> R = all(F, Xs), if R == true ; R == false -> R end end.
Of course SML and Haskell don't need to check because the type system means that the code that gets past the compiler couldn't possibly fail such a check. But Lisp and Smalltalk and Python don't bother making any such check, even though it _might_ fail if it _were_ done.
Skipping the check on R doesn't seem to be much of a problem in practice. Loss of tail recursion here IS a problem.