[erlang-questions] style question - best way to test multiple non-guard conditions sequentially

296 views
Skip to first unread message

Jonathan Leivent

unread,
Jun 19, 2013, 2:23:49 PM6/19/13
to erlang-q...@erlang.org
Suppose one has multiple conditions that are not guards that need to be
tested sequentially (meaning, only test condition N+1 after condition N
tests false) in a function. Using a case or if in a simplistic way
would then produce considerable nesting, especially as the number of
conditions increases above 2 or 3.

Is there some way in Erlang to write such code without either breaking
up the function clause or using high degrees of nesting?

The closest I can come is something like:

fun(...) ->
C1 = cond1(...),
C2 = C1 orelse cond2(...),
C3 = C2 orelse cond3(...),
if C1 -> ...;
C2 -> ...;
C3 -> ...;
true -> ...
end.

Which works, but looks rather cryptic. It also seems like it is doing
all of the branching at least twice.

I guess this also implies the question: why does Erlang require the
conditions in an if statement to be guards?

-- Jonathan
_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Dmitry Kolesnikov

unread,
Jun 19, 2013, 5:50:20 PM6/19/13
to Jonathan Leivent, erlang-q...@erlang.org
Hello,

I am solving this problem in following way:

run() ->
maybe_cond1(cond1(), …).

maybe_cond1(false, …) ->
maybe_cond2(cond2(), …);
maybe_cond1(true, …) ->
% do cond1.

maybe_cond2(false, …) ->
maybe_cond2(cond3(), …);
maybe_cond2(true, …) ->
% do cond2.

maybe_cond3(false, …) ->
% do failure
maybe_cond3(true, …) ->
% do cond3.

this approach allows you to have as many conditions as you like.
The code can be simplified by using lists:fold and closers e.g.

lists:foldl(fun assert/2, false, [fun cond1/0, fun cond2/0, fun cond3/0]).

assert(_, true) ->
true;
assert(Fun, false) ->
Fun().

cond1() ->
case is_cond1() of
true ->
% do condition 1
true;
false ->
false
end.



but I do prefer the first approach.


Best Regards,
Dmitry

Siraaj Khandkar

unread,
Jun 19, 2013, 7:57:37 PM6/19/13
to erlang-q...@erlang.org
I like the fold approach, but a bit more specialized:


fold_until_error([], X) ->
{ok, X};
fold_until_error([F|Fs], X) ->
case F(X) of
{ok, Y} -> fold_until_error(Fs, Y);
{error, _}=E -> E
end.

do_stuff(Stuff) ->
Conditions =
[ fun c1/1
, ...
, fun cn/1
],
case fold_until_error(Conditions, Stuff) of
{ok, Data} -> ...;
{error, Reason} -> ...
end.


One can, of course, use a boolean() instead of ok|error tuples, but I
prefer it because you can know exactly what did not pass and for what
reason [1].


[1]: http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

Richard A. O'Keefe

unread,
Jun 19, 2013, 10:15:23 PM6/19/13
to Jonathan Leivent, erlang-q...@erlang.org

On 20/06/2013, at 6:23 AM, Jonathan Leivent wrote:

> Suppose one has multiple conditions that are not guards that need to be tested sequentially (meaning, only test condition N+1 after condition N tests false) in a function.

Ah hah! You want the long-ago-proposed 'cond' form.

pick([{Test,Act}|Rest]) ->
case Test()
of true -> Act()
; false -> f(Rest)
end.
> fun(...) ->
> C1 = cond1(...),
> C2 = C1 orelse cond2(...),
> C3 = C2 orelse cond3(...),
> if C1 -> ...;
> C2 -> ...;
> C3 -> ...;
> true -> ...
> end.

fun(...) ->
pick([
{fun () -> cond1... end,
fun () -> action1... end},
{fun () -> cond2... end,
fun () -> action2... end},
{fun () -> cond3... end,
fun () -> action3... end},
{fun () -> true end,
fun () -> action4... end}).

Choose your own name for this.

However, I cannot help feeling that in each specific instance
of this there would be something better to write instead. So
how about showing us a real example?
>
> I guess this also implies the question: why does Erlang require the conditions in an if statement to be guards?

Because back when Erlang was invented, there were no conditions that
were not guard tests *only*. For example, X > Y was not an expression.
There were no andalso or orelse.

Ah, the good old days, before Erlang tried to turn into something else
but got stuck half way.

I really must finish writing up the '-guard' declaration EEP.

Jonathan Leivent

unread,
Jun 20, 2013, 11:29:40 AM6/20/13
to Richard A. O'Keefe, erlang-q...@erlang.org
I was hoping for something that more syntactically resembles a classic
if/elsif/else block. Without closures.

I started playing with some macros, and came up with this:

-define(IF(C), try (C) andalso begin).
-define(ELSIF(C), true end orelse (C) andalso begin).
-define(ELSE, true end orelse begin).
-define(ENDIF, true end catch {if_result, IF_RESULT} -> IF_RESULT end).
-define(BREAK(R), throw({if_result, R})).

foo(X) ->
?IF(cond1(X))
expr1,
expr2,
?BREAK(expr3),
?ELSIF(cond2(X))
expr4,
expr5,
?BREAK(expr6),
?ELSIF(cond3(X))
expr7,
expr8,
?BREAK(expr9),
?ELSE
expr0,
expra,
?BREAK(exprb),
?ENDIF.

One downside is that emacs isn't smart enough to indent this correctly,
since it doesn't expand macros. Also, to play better with other things,
including other such ?IF statements in the same scope, the ?ENDIF macro
shouldn't bind a variable - but I haven't figured out how to make that
not happen.

>
> However, I cannot help feeling that in each specific instance
> of this there would be something better to write instead. So
> how about showing us a real example?

Anything that would have worked in an if statement were the conditions
just guards. In the case I'm interested in, the conditions are
predicates on an opaque type - so they can't be guards.

>>
>> I guess this also implies the question: why does Erlang require the conditions in an if statement to be guards?
>
> Because back when Erlang was invented, there were no conditions that
> were not guard tests *only*. For example, X > Y was not an expression.
> There were no andalso or orelse.

OK - I will then add a word to the above question: why does Erlang
*still* require the conditions in an if statement to be guards?

How hard is it to remove this restriction?

-- Jonathan

Anthony Ramine

unread,
Jun 20, 2013, 11:42:27 AM6/20/13
to Jonathan Leivent, erlang-q...@erlang.org
Because changing it at this point would be weird. And because guards have special semantics and can't fail.

Supporting expressions now would mean weird semantics like in comprehensions, where you can't distinguish a guard from an expression and know that they are compiled differently, or breaking existing code such as "if length(X) =:= 3 -> ... ; ... end": if X is not a list and that expression is evaluated with guard semantics, the first clause is skipped, whereas if it is evaluated with expression semantics, the code crashes.

If I understood things correctly, Erlang is supposed to have a cond Clauses end expression —try to evaluate "cond" in Erlang, it's a syntax error because cond is a reserved keyword—, which is the same as if but with tests compiled with expression semantics. Maybe it should be implemented for real.

Regards,

--
Anthony Ramine

Le 20 juin 2013 à 17:29, Jonathan Leivent a écrit :

> OK - I will then add a word to the above question: why does Erlang *still* require the conditions in an if statement to be guards?
>
> How hard is it to remove this restriction?

Jonathan Leivent

unread,
Jun 20, 2013, 12:27:25 PM6/20/13
to Anthony Ramine, erlang-q...@erlang.org
On 06/20/2013 11:42 AM, Anthony Ramine wrote:
> Because changing it at this point would be weird. And because guards have special semantics and can't fail.
>
> Supporting expressions now would mean weird semantics like in comprehensions, where you can't distinguish a guard from an expression and know that they are compiled differently, or breaking existing code such as "if length(X) =:= 3 -> ... ; ... end": if X is not a list and that expression is evaluated with guard semantics, the first clause is skipped, whereas if it is evaluated with expression semantics, the code crashes.
>

OK - that is surprising. That certainly changes my thinking about if
statements. Is this particular semantic difference between guards and
general conditions ever expressed in the reference manual? If so, I
certainly missed it.

If I had read some code that just said:

if length(X) =:= 3 -> ...

I would have inferred (incorrectly) that it would be an error for X to
not be a list.

> If I understood things correctly, Erlang is supposed to have a cond Clauses end expression —try to evaluate "cond" in Erlang, it's a syntax error because cond is a reserved keyword—, which is the same as if but with tests compiled with expression semantics. Maybe it should be implemented for real.

Perhaps that is a remnant of "the long-ago-proposed 'cond' form" that
Richard mentioned.

-- Jonathan

Jonathan Leivent

unread,
Jun 20, 2013, 1:33:11 PM6/20/13
to erlang-q...@erlang.org

> OK - that is surprising. That certainly changes my thinking about if
> statements. Is this particular semantic difference between guards and
> general conditions ever expressed in the reference manual? If so, I
> certainly missed it.

On closer inspection:

"If an arithmetic expression, a boolean expression, a short-circuit
expression, or a call to a guard BIF fails (because of invalid
arguments), the entire guard fails. "

Before understanding this semantic peculiarity of guards, the
parenthetical portion of the above statement in the reference manual
didn't make much of an impression on me. Now, I see it is conveying
this semantics. Actually, when I read that sentence alone, it still
doesn't quite do the trick. It's the following sentence:

"If the guard was part of a guard sequence, the next guard in the
sequence (that is, the guard following the next semicolon) will be
evaluated."

that completes the meaning by disambiguating "fails" as "evaluates to
false" instead of "throws an exception". Although, still, I'd probably
make the mistake of typing "length(0)." into the interpreter, seeing the
error, and think that this experience overrules what I might have
otherwise inferred from the manual. To further confuse matters (or,
maybe just me), many guards are is_type tests - which would seem to be
useful, but are not, as protection for other guards in the same guard
expression:

if is_list(X), length(X) =:= 3 -> ...

Previous to this discussion, the reference manual statement: "The reason
for restricting the set of valid expressions is that evaluation of a
guard expression must be guaranteed to be free of side effects." was the
one that I based my (mis)understanding of the guard/condition
distinction on. I could understand why that would be an important
restriction for guards on things like function and receive clauses, but
not so much on if statements - or, at least, why there would not be any
Erlang statement that would allow testing a sequence of arbitrary
conditions.

Doesn't guard semantics violate the Principle of Least Astonishment?

I'd be astonished if I was alone in thinking that...

Anthony Ramine

unread,
Jun 20, 2013, 6:07:46 PM6/20/13
to Jonathan Leivent, erlang-q...@erlang.org
I don't believe they do in places where guards are the only inhabitant.

I do believe they violate it in comprehensions; call these two functions with [foo]:

F = fun (L) -> [ X || X <- L, X rem 2 =:= 0 ],
G = fun (L) -> [ X || X <- L, _ = X rem 2 =:= 0 ].

Regards,

PS: Sorry for the double mail Jonathan, failed to reply to all.

--
Anthony Ramine

Richard A. O'Keefe

unread,
Jun 20, 2013, 7:01:49 PM6/20/13
to Jonathan Leivent, erlang-q...@erlang.org

On 21/06/2013, at 3:29 AM, Jonathan Leivent wrote:

> ?ELSE
> expr0,
> expra,
> ?BREAK(exprb),
> ?ENDIF.

The ?BREAK is rather horrible; to my mind, far more horrible than nice
clean obvious closures.

I wrote:
>
>>
>> However, I cannot help feeling that in each specific instance
>> of this there would be something better to write instead. So
>> how about showing us a real example?
>
> Anything that would have worked in an if statement were the conditions just guards. In the case I'm interested in, the conditions are predicates on an opaque type - so they can't be guards.

That's interesting, but I doubt that anyone ever thought
you _could_ have written your code as a simple 'if'.
*How about showing us a real example?*
As in, actual code.

Real examples often show opportunities for restructuring in
completely different ways.

Also, I'm having a hard time imagining so many sequentially
tested conditions that

case C1
of true -> A1
; false ->
case C2
of true -> A2
; false ->
...
end
end

is really hard to write. It's certainly what
cond C1 -> A1 ; C2 -> C2 ; ... end
would compile into, if it existed.


>> Because back when Erlang was invented, there were no conditions that
>> were not guard tests *only*. For example, X > Y was not an expression.
>> There were no andalso or orelse.
>
> OK - I will then add a word to the above question: why does Erlang *still* require the conditions in an if statement to be guards?
>
> How hard is it to remove this restriction?

Expressions and guards have radically different semantics.
It was a really bad mistake when people started making them
look more similar.

For example, tuple_size(T) div N =:= M doesn't raise an
exception when N is zero, it just *fails*.

Guards are part of pattern matching; in order to get efficient
pattern matching code it is important that the compiler should
be free to reorder matching and testing any way it likes; in
particular G1, G2 need not be tested in the order G1 then G2
and often shouldn't be tested in that order. But if these
could be called to user defined functions, such reordering
could break things.

It would probably be easier to design a new Erlang-like language
compiling to the same VM than to *coherently* unify guards and
expressions.

Jonathan Leivent

unread,
Jun 20, 2013, 7:21:15 PM6/20/13
to Anthony Ramine, erlang-q...@erlang.org
On 06/20/2013 06:07 PM, Anthony Ramine wrote:
> I don't believe they do in places where guards are the only inhabitant.
>
> I do believe they violate it in comprehensions; call these two functions with [foo]:
>
> F = fun (L) -> [ X || X <- L, X rem 2 =:= 0 ],
> G = fun (L) -> [ X || X <- L, _ = X rem 2 =:= 0 ].
>
> Regards,
>

You omitted the "end"s - but you obviously didn't mean to do that.

OK - so you just blew my mind. What is happening here? Let me see if I
can explain it:

In F, the "X rem 2" guard fails because X is not an integer - but it is
in a guard context, so no error is produced, the guard just evaluates to
false, and F([foo]) returns [].

In G, the "X rem 2" is in the rhs of an "assignment" - really a match
expression: that is no longer a guard context, so G([foo]) produces an
error instead.

Does that sound right?

Personally, I would stay away from using this
false-not-error-on-bad-type behavior of guards that are not patterns.
It's easy enough to use the is_type BIFs to make the meaning explicit
and far less astonishing:

H = fun (L) -> [X || X <- L, is_integer(X), X rem 2 =:= 0] end.

But, then I'm writing Erlang code that I expect to be read and
understood by non-Erlang programmers.

Is Dialyzer (or some other lint-like Erlang tool) customizable? If so,
I'd like to add a customization that flags such cases for my own use.

Richard A. O'Keefe

unread,
Jun 20, 2013, 7:45:15 PM6/20/13
to Jonathan Leivent, erlang-q...@erlang.org

On 21/06/2013, at 4:27 AM, Jonathan Leivent wrote:
> OK - that is surprising. That certainly changes my thinking about if statements. Is this particular semantic difference between guards and general conditions ever expressed in the reference manual? If so, I certainly missed it.

Yes it is.

Section 7.24 "Guard Sequences", where guards are described.

http://www.erlang.org/doc/reference_manual/expressions.html#id80042

At the end, there is this sentence:

If an arithmetic expression, a boolean expression, a short-circuit
expression, or a call to a guard BIF fails (because of invalid
arguments), the entire guard fails . If the guard was part of
a guard sequence, the next guard in the sequence (that is, the
guard following the next semicolon) will be evaluated.


It's also explained clearly in the Erlang books I've read.

Anthony Ramine

unread,
Jun 20, 2013, 8:34:08 PM6/20/13
to Jonathan Leivent, erlang-questions@erlang.org Questions
Err, sorry again Jonathan.

Forwarding to list.

--
Anthony Ramine

Le 21 juin 2013 à 02:33, Anthony Ramine a écrit :

> Indeed, sorry.
>
>> OK - so you just blew my mind. What is happening here? Let me see if I can explain it:
>>
>> In F, the "X rem 2" guard fails because X is not an integer - but it is in a guard context, so no error is produced, the guard just evaluates to false, and F([foo]) returns [].
>>
>> In G, the "X rem 2" is in the rhs of an "assignment" - really a match expression: that is no longer a guard context, so G([foo]) produces an error instead.
>>
>> Does that sound right?
>
> Indeed, a match is an expression which is not a guard, thus the filter is compiled with expression semantics.
>
>> Personally, I would stay away from using this false-not-error-on-bad-type behavior of guards that are not patterns. It's easy enough to use the is_type BIFs to make the meaning explicit and far less astonishing:
>>
>> H = fun (L) -> [X || X <- L, is_integer(X), X rem 2 =:= 0] end.
>>
>> But, then I'm writing Erlang code that I expect to be read and understood by non-Erlang programmers.
>>
>> Is Dialyzer (or some other lint-like Erlang tool) customizable? If so, I'd like to add a customization that flags such cases for my own use.
>
> What could be done is to compile every filter as an expression and introduce a new kind of expression, something like "when Guard end", which would compile Guard with guard semantics.
>
> What's good:
>
> Guard semantics usable everywhere, with full power and disjunction and conjunctions, something not doable in current comprehensions.
>
> Syntax clues about what has guard semantics and what has expression semantics.
>
> What's bad:
>
> Breaking backwards compatibility. Huge problem. So huge I wouldn't be surprised and I would rather expect that the semantics comprehension filters will never change.
>
> Nevertheless, I do think it is feasible in multiple OTP versions, progressively.
>
> Regards,

Jonathan Leivent

unread,
Jun 20, 2013, 8:34:43 PM6/20/13
to Richard A. O'Keefe, erlang-q...@erlang.org
On 06/20/2013 07:01 PM, Richard A. O'Keefe wrote:
>
> On 21/06/2013, at 3:29 AM, Jonathan Leivent wrote:
>
>> ?ELSE
>> expr0,
>> expra,
>> ?BREAK(exprb),
>> ?ENDIF.
>
> The ?BREAK is rather horrible; to my mind, far more horrible than nice
> clean obvious closures.

I never claimed it was a good solution. Rather, it's the lack of an
unrestricted sequential multi-conditional statement that made me
consider such things.

>
> I wrote:
>>
>>>
>>> However, I cannot help feeling that in each specific instance
>>> of this there would be something better to write instead. So
>>> how about showing us a real example?
>>
>> Anything that would have worked in an if statement were the conditions just guards. In the case I'm interested in, the conditions are predicates on an opaque type - so they can't be guards.
>
> That's interesting, but I doubt that anyone ever thought
> you _could_ have written your code as a simple 'if'.
> *How about showing us a real example?*
> As in, actual code.

I have a data structure that is implemented by a module called
"proposal" - part of a distributed agreement protocol. There is a
receive in a separate module that is a gen_server behavior that needs to
examine proposals in msgs using API predicates exported from the
proposal module. So, the code would be, if "if" allowed:

loop(State) ->
receive
{propose, From, Proposal} ->
if proposal:round(Proposal) < State#state.current_round -> ...;
proposal:is_subsumed_by(Proposal,
State#state.last_proposal) -> ...;

etc. I'm sure you get the idea.

>
> Real examples often show opportunities for restructuring in
> completely different ways.

The problem is, once you have multiple conditions that are not guards,
and that you want to examine sequentially, what simple structure can you
come up with in Erlang?

>
> Also, I'm having a hard time imagining so many sequentially
> tested conditions that
>
> case C1
> of true -> A1
> ; false ->
> case C2
> of true -> A2
> ; false ->
> ...
> end
> end
>
> is really hard to write. It's certainly what
> cond C1 -> A1 ; C2 -> C2 ; ... end
> would compile into, if it existed.

Oh - certainly - there's only 4 in my current case, but as the design
expands, there could easily be more. But, 4 is plenty already ugly
nestingwise.

I'm probably only doing a mediocre job of justifying an if/elsif/else
construct.

>
>
>>> Because back when Erlang was invented, there were no conditions that
>>> were not guard tests *only*. For example, X > Y was not an expression.
>>> There were no andalso or orelse.
>>
>> OK - I will then add a word to the above question: why does Erlang *still* require the conditions in an if statement to be guards?
>>
>> How hard is it to remove this restriction?
>
> Expressions and guards have radically different semantics.
> It was a really bad mistake when people started making them
> look more similar.
>
> For example, tuple_size(T) div N =:= M doesn't raise an
> exception when N is zero, it just *fails*.
>
> Guards are part of pattern matching; in order to get efficient
> pattern matching code it is important that the compiler should
> be free to reorder matching and testing any way it likes; in
> particular G1, G2 need not be tested in the order G1 then G2
> and often shouldn't be tested in that order. But if these
> could be called to user defined functions, such reordering
> could break things.
>
> It would probably be easier to design a new Erlang-like language
> compiling to the same VM than to *coherently* unify guards and
> expressions.

OK - now that I understand how different guards are, I would not at all
suggest that if statements should be modified to allow normal
conditional expressions.

However, why was that "cond" expression dropped or never fully implemented?

-- Jonathan

Anthony Ramine

unread,
Jun 20, 2013, 8:35:22 PM6/20/13
to Richard A. O'Keefe, erlang-q...@erlang.org
Guard semantics are not mentioned in the documentation about comprehension filters though.

And I would rather have it removed than documented, but that's another story which I tell in another mail.

--
Anthony Ramine

Richard A. O'Keefe

unread,
Jun 20, 2013, 9:34:53 PM6/20/13
to Anthony Ramine, erlang-q...@erlang.org

On 21/06/2013, at 12:35 PM, Anthony Ramine wrote:

> Guard semantics are not mentioned in the documentation about comprehension filters though.

Fair enough. And I'm not the least little bit happy about the fact
that comprehension filters aren't guards.

I would have expected
'[' <expr> '||' <stuff> ']'
where
<stuff> ::= <stuff> ',' <stuff>
| <pattern> '<-' <expression>
| <pattern> '=' <expression>
| <possibly parenthesised guard>

We can tolerate a thing being one way (X < Y is only a guard, and if X or Y
raises an exception, it just fails) or all the other way (guards and
expressions are identical), but a mixture is bound to confuse.

Richard A. O'Keefe

unread,
Jun 20, 2013, 10:25:22 PM6/20/13
to Jonathan Leivent, erlang-q...@erlang.org

On 21/06/2013, at 12:34 PM, Jonathan Leivent wrote:
> I have a data structure that is implemented by a module called "proposal" - part of a distributed agreement protocol. There is a receive in a separate module that is a gen_server behavior that needs to examine proposals in msgs using API predicates exported from the proposal module. So, the code would be, if "if" allowed:
>
> loop(State) ->
> receive
> {propose, From, Proposal} ->
> if proposal:round(Proposal) < State#state.current_round -> ...;
> proposal:is_subsumed_by(Proposal,
> State#state.last_proposal) -> ...;
>
> etc. I'm sure you get the idea.

"Getting the idea" is not the point.
"Seeing the details" is.

> The problem is, once you have multiple conditions that are not guards, and that you want to examine sequentially, what simple structure can you come up with in Erlang?

Well, for one thing, it might be an idea to rip the branching structure
out of the receive.

loop(State) ->
receive
{propose, From, Proposal} ->
handle_proposal(Proposal, State, From)
; ...
end.

handle_proposal(Proposal, State, From) ->
Proposal_Round = proposal:round(Proposal),
if Proposal_Round < State#state.current_round ->
handle_old_proposal(Proposal, State, From)
; true ->
handle_new_proposal(Proposal, State, From)
end.

handle_new_proposal(Proposal, State, From) ->
case proposal:is_subsumed_by(Proposal, State#state.last_proposal)
of true -> handle_subsumed_proposal(Proposal, State, From)
; false -> handle_interesting_proposal(Proposal, State, From)
end.

And that this point, I stop, because my experience tells me
"BEWARE OF BOOLEANS!"

If the case where Y subsumes X is interesting, how about the
case where X subsumes Y? Why isn't this something like

case proposal:relative_generality(Proposal, State#state.last_proposal)
of identical ->
; more_general ->
; more_specific ->
; incomparable ->
end

What is common between the various branches?
Could the proposal: module be restructured to provide
additional/alternative queries that make this code
easier to express?

> However, why was that "cond" expression dropped or never fully implemented?

I've often wondered that myself.

Jonathan Leivent

unread,
Jun 20, 2013, 11:41:06 PM6/20/13
to Richard A. O'Keefe, erlang-q...@erlang.org
On 06/20/2013 10:25 PM, Richard A. O'Keefe wrote:
> ...
>> The problem is, once you have multiple conditions that are not guards, and that you want to examine sequentially, what simple structure can you come up with in Erlang?
>
> Well, for one thing, it might be an idea to rip the branching structure
> out of the receive.

Yeah, sure. But, I'm forced to do that, instead of deciding to do it if
and when it's a good way to modularize the code. In this particular
case, I'd like to see the full conditional all at once, compactly,
without obfuscating plumbing, preferably in the receive statement with
the other protocol messages. I think it would just help me think
clearly about this key top level of the protocol's decision tree.

Again, I haven't done a good job justifying the existence of
unrestricted sequential conditional statements. But, considering their
ubiquity in virtually all other programming languages, do you really
think Erlang's omission of such a construct is a "feature"?

Although, you might not be trying to argue that.

Anyway, I appreciate the help with finding an alternative coding scheme
- but if it doesn't exhibit the compactness I was hoping for - that I
can visualize in an Erlang-like pseudocode - then this isn't really
helping here.

-- Jonathan

Siraaj Khandkar

unread,
Jun 21, 2013, 12:38:50 AM6/21/13
to Jonathan Leivent, erlang-q...@erlang.org
On Jun 20, 2013, at 23:41, Jonathan Leivent <jlei...@comcast.net> wrote:

> On 06/20/2013 10:25 PM, Richard A. O'Keefe wrote:
>> ...
>>> The problem is, once you have multiple conditions that are not guards, and that you want to examine sequentially, what simple structure can you come up with in Erlang?
>>
>> Well, for one thing, it might be an idea to rip the branching structure
>> out of the receive.
>
> Yeah, sure. But, I'm forced to do that, instead of deciding to do it if and when it's a good way to modularize the code. In this particular case, I'd like to see the full conditional all at once, compactly, without obfuscating plumbing, preferably in the receive statement with the other protocol messages. I think it would just help me think clearly about this key top level of the protocol's decision tree.
>
> Again, I haven't done a good job justifying the existence of unrestricted sequential conditional statements.

You can fold over an unrestricted list of sequentially ordered closures.

Other than being different from if-then-else, why do you find that approach so unappealing?

Richard A. O'Keefe

unread,
Jun 21, 2013, 1:04:13 AM6/21/13
to Jonathan Leivent, erlang-q...@erlang.org

On 21/06/2013, at 3:41 PM, Jonathan Leivent wrote:
>> Well, for one thing, it might be an idea to rip the branching structure
>> out of the receive.
>
> Yeah, sure. But, I'm forced to do that, instead of deciding to do it if and when it's a good way to modularize the code.

Someone has a blog where they looked at how to write Erlang code
so that it's _obviously_ right, and ripping things into smaller
pieces was part of it. If you can't name it, you don't understand it.


> In this particular case, I'd like to see the full conditional all at once, compactly, without obfuscating plumbing, preferably in the receive statement with the other protocol messages.

"compactly" and "in the receive statement" is a contradiction.

I've seen too many Erlang state loops with giant functions stretching
over multiple pages. I'm not talking about *adding* obfuscation, I'm
talking about *removing* it.

> I think it would just help me think clearly about this key top level of the protocol's decision tree.
>
> Again, I haven't done a good job justifying the existence of unrestricted sequential conditional statements. But, considering their ubiquity in virtually all other programming languages, do you really think Erlang's omission of such a construct is a "feature"?

But we *STILL* haven't seen any *ACTUAL* code to tell whether anyone
other than you would experience a problem with the code, or whether
there might be a completely different way to structure the code.

No, I don't think that it's a feature. Arguably, Erlang would have been
better off without 'if' entirely.

Let's face it "everybody else does it" is not a good argument for anything.
"Virtually all other programming languages" have bounded size integers.
"Virtually all other programming languages" rely *massively* on assignment
statements.

You and I have now spent more time talking about this than it would take
to just write the wretched code and get on with our lives.

In a way I *do* want to say that a language that won't let you code
automatically like "virtually all other programming languages" is an
opportunity to try to think differently about what you're doing.
That doesn't mean the old way is _wrong_; maybe the language really is
deficient. But an opportunity to think differently is to be taken.

I was about to say "Any time I find myself yearning for a Fortran feature,
I stop and think", but actually, Fortran 2008 has finally picked up the
'let' feature of (most) functional languages -- although in typical
Fortran fashion it's called ASSSOCIATE.

Without seeing the actual code, nobody else can tell whether 'compactness'
is really a virtue in this case, or whether it gets in the way of 'clarity'.

I do repeat that more than 40 years after meeting formal logic,
I love logic, but am extremely suspicious of Boolean expressions in
functional languages. Most Boolean-valued functions that have been
discussed in this mailing list turned out to be things that would be
better conceived some other way (like a Haskell Maybe or Either or
something with more than two possibilities).

How can I tell whether this is the case in your problem without seeing
the code? (Yes, I know, old Johnny One-Note harping on a single string.)
>
> Anyway, I appreciate the help with finding an alternative coding scheme - but if it doesn't exhibit the compactness I was hoping for - that I can visualize in an Erlang-like pseudocode - then this isn't really helping here.

I have to leave in order to get to my father-in-law's birthday party.
This is what I have by way of patches to erl_parse.yrl so far.

37c37,40
< if_expr if_clause if_clauses case_expr cr_clause cr_clauses receive_expr
---
> if_expr if_clause if_clauses
> case_expr cr_clause cr_clauses
> cond_expr cond_clause cond_clauses
> receive_expr
56c59
< 'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
---
> 'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'cond' 'if' 'of' 'receive' 'when'
277a281
> expr_max -> cond_expr : '$1'.
387a392,398
> cond_expr -> 'cond' cond_clauses 'end' : mk_cond($2).
>
> cond_clauses -> cond_clause : ['$1'].
> cond_clauses -> cond_clause ';' cond_clauses : ['$1' | '$3'].
>
> cond_clause -> expr clause_body : {clause,?line('$1'),'$1','$2'}.
>
1103a1115,1124
>
> mk_cond([{clause,Line,Expr,Body}]) ->
> {'case',Line,Expr,[
> {clause,Line,[{atom,Line,true}],[],Body}]}.
> mk_cond([{clause,Line,Expr,Body}|Rest]) ->
> [{clause,Next,_,_}|_] = Rest,
> {'case',Line,Expr,[
> {clause,Line,[{atom,Line,true}],[],Body},
> {clause,Next,[{atom,Line,false}],[],[mk_cond(Rest)]}]}.

erlc is happy with this, but it has had no other testing.

Siraaj Khandkar

unread,
Jun 21, 2013, 1:23:29 AM6/21/13
to Richard A. O'Keefe, erlang-q...@erlang.org
On Jun 21, 2013, at 1:04, "Richard A. O'Keefe" <o...@cs.otago.ac.nz> wrote:


On 21/06/2013, at 3:41 PM, Jonathan Leivent wrote:
Well, for one thing, it might be an idea to rip the branching structure
out of the receive.

Yeah, sure.  But, I'm forced to do that, instead of deciding to do it if and when it's a good way to modularize the code.

Someone has a blog where they looked at how to write Erlang code
so that it's _obviously_ right, and ripping things into smaller
pieces was part of it.  If you can't name it, you don't understand it.




I do repeat that more than 40 years after meeting formal logic,
I love logic, but am extremely suspicious of Boolean expressions in
functional languages.  Most Boolean-valued functions that have been
discussed in this mailing list turned out to be things that would be
better conceived some other way (like a Haskell Maybe or Either or
something with more than two possibilities).

Anthony Ramine

unread,
Jun 21, 2013, 4:21:12 AM6/21/13
to Richard A. O'Keefe, erlang-questions@erlang.org Questions
Hello Richard,

What we currently have:

qual -> pat '<-' expr.
qual -> bin_pat '<=' expr.
qual -> expr.

I would keep it as is but compile every filter as an expression and the following grammar rule:

expr -> 'when' guard 'end'.

And then I would also add two generators which do not skip elements but crash instead:

qual -> pat '<:-' expr.
qual -> bin_pat '<:=' expr.

I do like the different direction you took though. Allowing only guards mean matches can be handled differently, thus one could write X = foo() instead of begin X = foo(), true end.

Regards,

--
Anthony Ramine

Anthony Ramine

unread,
Jun 21, 2013, 4:22:37 AM6/21/13
to Richard A. O'Keefe, erlang-q...@erlang.org
I'll make a proper implementation of it this week-end. I would have done it earlier if you had written an EEP :)

--
Anthony Ramine

Le 21 juin 2013 à 07:04, Richard A. O'Keefe a écrit :

> This is what I have by way of patches to erl_parse.yrl so far.

Richard A. O'Keefe

unread,
Jun 23, 2013, 6:17:05 PM6/23/13
to Anthony Ramine, erlang-questions@erlang.org Questions

On 21/06/2013, at 8:21 PM, Anthony Ramine wrote:

> qual -> pat '<-' expr.
> qual -> bin_pat '<=' expr.
> qual -> expr.
>
> And then I would also add two generators which do not skip elements but crash instead:
>
> qual -> pat '<:-' expr.
> qual -> bin_pat '<:=' expr.

I don't understand what these are supposed to do.
Would you care to say more?
>
> I do like the different direction you took though. Allowing only guards mean matches can be handled differently, thus one could write X = foo() instead of begin X = foo(), true end.

I've been using X <- [foo()].

Anthony Ramine

unread,
Jun 24, 2013, 8:45:43 AM6/24/13
to Richard A. O'Keefe, erlang-questions@erlang.org Questions
Forgot to Cc the list, again.

--
Anthony Ramine

Le 24 juin 2013 à 13:24, Anthony Ramine a écrit :

> Hello Richard,
>
> List = [foo],
> [] = [ X || {bar,X} <- List ],
> {'EXIT',_} = (catch [ X || {bar,X} <:- List ]).
>
> Basically, the <:- generator doesn't have a lc([_|T]) -> lc(T) skipping clause.
>
> Regards,

Daniil Churikov

unread,
Jun 24, 2013, 8:51:47 AM6/24/13
to erlang-pr...@googlegroups.com, erlang-q...@erlang.org
Hello Jonathan, hello list.

In _some_ cases to avoid such validation nesting I use try/catch and pattern matching:

    try
        v1 = proplists:get_value(p1, L),
        v2 = proplists:get_value(p2, L),
        v3 = proplists:get_value(p3, L)
    catch
        _:_ ->
            %% handle exception here and return
            %% or re-throw it
            false
    end

But you should clearly understand what code you will wrap with try/catch. It is possible hide some errors inside this block,
so i try not not wrap complex logic inside, only some input validators.

Richard A. O'Keefe

unread,
Jun 24, 2013, 10:07:00 PM6/24/13
to Siraaj Khandkar, erlang-q...@erlang.org

On 21/06/2013, at 5:23 PM, Siraaj Khandkar wrote:
> http://www.gar1t.com/blog/2012/06/10/solving-embarrassingly-obvious-problems-in-erlang/
> http://existentialtype.wordpress.com/2011/03/15/dont-mention-equality/
> http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

I had those in mind, so thanks for finding them.

There are a number of bug prevention tricks that are unreasonably
effective. Compare

class Mutex {
...
bool trylock();
};

if (my_mutex->trylock()) {
// is the lock held here or not?
}

with

class Mutex {
enum Status {This_Thread_Has_It, Another_Thread_Has_It};
...
Status trylock();
};

if (my_mutex->trylock() == Mutex::This_Thread_Has_It) {
// we don't need to think here; we _know_.
}

And then in functional languages, so often we find that there's more
information to return in at least one of the cases. Harper points
out the benefits of
case List
of [] -> ...
; [Head|Tail] -> ...
end
compared with
if null(List) -> ...
; true -> Head = hd(List), tl = tl(List), ...
end

I think there's a felt difference (not necessarily a fundamental difference)
between languages with pattern matching -- where constructing "rich answers",
returning them, and dispatching on them *feels* cheap -- and languages
without -- where none of these is true.

For example, even though I'm suspicious of Booleans, my Smalltalk code is
riddled with #ifTrue:ifFalse:. (About 6% of the SLOC, in fact.) A good
deal of that has to do with the fact that you can't return multiple
answers at the same time from a Smalltalk method without creating a new
object. (The fact that Smalltalk didn't have var parameters was
excusable in an experimental language; the way Java made their absence
a religious dogma is not. One of the things C# got right.)

Anthony Ramine

unread,
Jul 7, 2013, 1:21:49 PM7/7/13
to Richard A. O'Keefe, erlang-q...@erlang.org
Hello,

My implementation of cond is unfortunately still a work in progress [1].

I couldn't decide which error should be thrown when the cond test doesn't return a boolean but I have found some references to cond expressions in an old paper [2] (page 37) and decided to stick to their choices.

Regards,

[1] https://github.com/nox/otp/compare/erlang:master...cond
[2] http://babel.ls.fi.upm.es/~fred/thesis.pdf

--
Anthony Ramine

o...@cs.otago.ac.nz

unread,
Jul 7, 2013, 6:04:02 PM7/7/13
to Anthony Ramine, erlang-q...@erlang.org

> I couldn't decide which error should be thrown when the cond test doesn't
> return a boolean but I have found some references to cond expressions in
> an old paper [2] (page 37) and decided to stick to their choices.

To be honest, I couldn't see any good reason to make
cond E1 -> B1 ; ... ; En -> Bn end
any different from
case E1 of true -> B1 ; false ->
...
case En of true -> Bn
end
...
end
I see great virtue in there being *no* difference whatsoever
in the semantics of the two, so that the choice is *solely*
stylistic and rewriting one into the other (either way) *cannot*
break a working program.

Having looked at that reference, I do not see any advantage for
the more complicated version. As long as you can find out
*where* the error was, it just isn't _useful_ to know 'it was a
cond' rather than 'it was a case'; a program catching errors
would never want to treat them differently, and a human who has
been told _where_ can instantly see _what_.

Anthony Ramine

unread,
Jul 8, 2013, 2:04:34 AM7/8/13
to o...@cs.otago.ac.nz, erlang-q...@erlang.org
With that logic, I see no reason to make if G -> B end different from case {} of {} when G -> B end.

Expanding cond to nested cases earlier in the compilation would mean confusing and misguided warning, e.g. "cond foobar -> ok end" would trigger the warning "no clause will ever match"; instead of the clearer "this clause will crash".

--
Anthony Ramine

Richard A. O'Keefe

unread,
Jul 8, 2013, 2:22:02 AM7/8/13
to Anthony Ramine, erlang-q...@erlang.org

On 8/07/2013, at 6:04 PM, Anthony Ramine wrote:

> With that logic, I see no reason to make if G -> B end different from case {} of {} when G -> B end.

Neither do I. It is excess complexity that we really don't need,
and it inhibits a range of source-to-source transformations that
ought not to be inhibited.

If I am told which *function clause* a selection failure is in,
better still, if I am told which *line* it is in, no distinction
between if/case/cond in exceptions is of any real help. The
*only* time it is helpful is when you have one 'if' and one 'case'.
Have *two* 'if's or *two* 'case's, and it's useless.

>
> Expanding cond to nested cases earlier in the compilation would mean confusing and misguided warning, e.g. "cond foobar -> ok end" would trigger the warning "no clause will ever match"; instead of the clearer "this clause will crash".

You have presented a good argument for rewording the error message.

That's all.

Anthony Ramine

unread,
Jul 8, 2013, 4:35:25 AM7/8/13
to Richard A. O'Keefe, erlang-q...@erlang.org
No this example was just confusing, what I meant is that if you need a warning "no clause will ever match" in the case of an explicit "case foobar of true -> ok; false -> ok end" and a warning "this clause will crash" in the case of "cond foobar -> ok; true -> ok end". There is no rewording that makes the warning fit in both situations, they need to be handled separately.

In my implementation I decided to annotate the Core Erlang cases to which a cond is compiled with the atom "cond_case".

--
Anthony Ramine

Richard A. O'Keefe

unread,
Jul 8, 2013, 6:12:05 PM7/8/13
to Anthony Ramine, erlang-q...@erlang.org

On 8/07/2013, at 8:35 PM, Anthony Ramine wrote:

> No this example was just confusing, what I meant is that if you need a warning "no clause will ever match" in the case of an explicit "case foobar of true -> ok; false -> ok end" and a warning "this clause will crash" in the case of "cond foobar -> ok; true -> ok end". There is no rewording that makes the warning fit in both situations, they need to be handled separately.

In the English text here you have an "if" with no consequent.

case foobar of true -> ok ; false -> ok end

Yes, it is appropriate to say "No matching clause" here.

As for

cond foobar -> {ok,1} ; true-> {ok,2} end

this should *BE*

case foobar
of true -> {ok,1}
; false -> case true of true -> {ok,2} end
end

and no error message is appropriate here because there
*is* a clause that will match and be selected. It is only
true that the two situations need separate handling because
one of them is not an error.

Another not-entirely-unreasonable translation of

cond C1 -> B1 ; ... ; Cn -> Bn end

would be

case (C1 andalso true)
of true -> B1
; false -> ...
case (Cn andalso true)
of true -> Bn
end
end

and in this case you might just possibly get a warning from
the compiler that might just possibly actually be useful
(unlike 'this clause will crash'): you might be told
"expression cannot be Boolean".

It could *never* be appropriate to say "this clause will crash"
for the simple reason that it's *not* the clause that would
crash but the *condition* of the clause. "This condition cannot
be Boolean" might make sense, but not the other.

Anthony Ramine

unread,
Jul 9, 2013, 6:00:10 AM7/9/13
to Richard A. O'Keefe, erlang-q...@erlang.org
Which Erlang clause matches here?

--
Anthony Ramine

Le 9 juil. 2013 à 00:12, Richard A. O'Keefe a écrit :

> this should *BE*
>
> case foobar
> of true -> {ok,1}
> ; false -> case true of true -> {ok,2} end
> end
>
> and no error message is appropriate here because there
> *is* a clause that will match and be selected.

Richard A. O'Keefe

unread,
Jul 9, 2013, 6:42:04 PM7/9/13
to Anthony Ramine, erlang-q...@erlang.org

On 9/07/2013, at 10:00 PM, Anthony Ramine wrote:

> Which Erlang clause matches here?

None. I was completely out to lunch.

Robert Virding

unread,
Jul 15, 2013, 7:39:56 AM7/15/13
to Anthony Ramine, erlang-q...@erlang.org
Sorry for getting in this discussion late, on holidays.

Just an historical curiosity. Before we had 'if' and we needed something if-ish this is exactly what we would do. We would use a case with a trivial expression and clause patterns which would always match and all the work was done in the guards. It looked so horrific that we added 'if'.

Robert

Robert Virding

unread,
Jul 15, 2013, 7:51:30 AM7/15/13
to Anthony Ramine, erlang-q...@erlang.org
If you are implementing a 'cond' then there is one extension which is quite practical though it breaks the purity of cond. It is to add the possibility of having an explicit match in the test, not just whether it returns 'true' of 'false'. Also having any variables in the match accessible in the body. I don't really have a good suggestion for a syntax except perhaps to extend 'cond' tests with a "<pattern> = <expr>" option, or perhaps "<pattern> ?= <expr>". If the match fails then no error is generated and the next test is tried.

I added this to LFE and it is quite useful.

Something like this was discussed a while back as a general extension to guards but I can't remember the syntax. It would only allow approved guard expressions.

Robert


----- Original Message -----
> From: "Anthony Ramine" <n.o...@gmail.com>
> To: "Richard A. O'Keefe" <o...@cs.otago.ac.nz>
> Cc: erlang-q...@erlang.org
> Sent: Monday, 8 July, 2013 10:35:25 AM
> Subject: Re: [erlang-questions] style question - best way to test multiple non-guard conditions sequentially
>

Richard A. O'Keefe

unread,
Jul 15, 2013, 6:30:04 PM7/15/13
to Robert Virding, erlang-q...@erlang.org
When there is a choice between two constructs such as
if G -> Bt ; true -> Bf end
and case {} of {} when G -> Bt ; {} when true -> Bf end
that *LOOKS* as though it is purely stylistic,
it should *BE* purely stylistic.

Consider
if E -> Bt ; true -> Bf end
and
cond E -> Bt ; true -> Bf end

where E is such that both forms are syntactically legal.
It *looks* as though the choice is purely stylistic.
So it should *be* purely stylistic, otherwise it would
be gratuitously difficult to change from one to the other.
And that means that

cond foobar -> Bt ; true -> Bf end

should execute Bf and NOT complain about foobar.
(Yes, my prototype got this wrong. I just hadn't thought
it through clearly.)

Anthony Ramine

unread,
Jul 15, 2013, 9:16:52 PM7/15/13
to Richard A. O'Keefe, erlang-q...@erlang.org
I disagree.

In the case of 'if', the tests are disjunctions of conjunctions of guards; whereas in the case of 'cond' they should be boolean tests. So what is a valid 'if' clause may not be a 'cond' one.

Furthermore, in the case of 'if', the fact that foobar just fails silently is because guard semantics are used: the very thing that is not used in 'cond' expressions.

--
Anthony Ramine

Anthony Ramine

unread,
Jul 15, 2013, 9:20:19 PM7/15/13
to Richard A. O'Keefe, erlang-q...@erlang.org
And reading your mail again, are you sure you meant that Bf should be executed? Whether in if or cond, that definitely sounds wrong.

--
Anthony Ramine

Anthony Ramine

unread,
Jul 15, 2013, 9:21:12 PM7/15/13
to Richard A. O'Keefe, erlang-q...@erlang.org
Nevermind that part, mistook Bt for Bf.

--
Anthony Ramine

Anthony Ramine

unread,
Dec 15, 2013, 1:07:16 PM12/15/13
to Richard A. O'Keefe, Jonathan Leivent, erlang-q...@erlang.org
Hello,

I’ve thought about this again and realised that there is a technical reason not to expand cond expressions to nested cases directly. That reason is scoping rules.

Let’s assume we compile this expression naively to nested cases:

cond X = false -> X;
X = true -> X
end

=>

case X = false of
true -> X;
false ->
case X = true of
true -> X;
end
end.

I would be *extremely* surprised that such an expression would crash with badmatch instead of returning true as it should.

Regards,

--
Anthony Ramine

Richard Carlsson

unread,
Dec 16, 2013, 5:32:52 AM12/16/13
to Anthony Ramine, Richard A. O'Keefe, Jonathan Leivent, erlang-q...@erlang.org
On 2013-12-15 19:07 , Anthony Ramine wrote:
> Hello,
>
> I’ve thought about this again and realised that there is a technical reason not to expand cond expressions to nested cases directly. That reason is scoping rules.
>
> Let’s assume we compile this expression naively to nested cases:
>
> cond X = false -> X;
> X = true -> X
> end
>
> =>
>
> case X = false of
> true -> X;
> false ->
> case X = true of
> true -> X;
> end
> end.
>
> I would be *extremely* surprised that such an expression would crash with badmatch instead of returning true as it should.
>
> Regards,
>

Yes, these complications are the reasons why cond never got implemented
beyond experimenting with the expansion you showed above. It needs
clearly specified scoping rules, and support in all tools above the Core
Erlang level. And nobody seemed to have the time to do that.

/Richard

Anthony Ramine

unread,
Dec 16, 2013, 5:56:12 AM12/16/13
to Richard Carlsson, Jonathan Leivent, erlang-q...@erlang.org
It’s done finished yet, but I’m on it:

https://github.com/nox/otp/tree/cond

--
Anthony Ramine

Anthony Ramine

unread,
Dec 16, 2013, 6:00:35 AM12/16/13
to Richard Carlsson, Jonathan Leivent, erlang-q...@erlang.org
That should have been « it’s not finished yet ».

--
Anthony Ramine

Max Lapshin

unread,
Dec 16, 2013, 6:00:44 AM12/16/13
to Anthony Ramine, Jonathan Leivent, erlang-q...@erlang.org
Frankly speaking, it is the way that if should been done =(

It is a good feature!

Robert Virding

unread,
Dec 25, 2013, 9:57:27 PM12/25/13
to Anthony Ramine, Jonathan Leivent, erlang-q...@erlang.org
One way around this problem is to handle '=' specially if it at the "top-level" in the test and not compile it naively. You would then say that variables occurring in the pattern would follow the same scoping rules as variables in if/case/receive clauses: if they are bound in all clauses then they are exported otherwise they are only bound in that clause. To do it that way also makes the translation simple. Your case could become:

case false of
X -> X;
_ ->
case true of
X -> true
end
end

In this case there would be no badmatch and X would be exported. If the match fails the next clause tried.

If users explicitly use '=' deep in their expressions then they are on their own, cond only handles at the top-level. Even with this limitation cond would be useful.

Robert

----- Original Message -----
> From: "Anthony Ramine" <n.o...@gmail.com>
> To: "Richard Carlsson" <carlsson...@gmail.com>
> Cc: "Jonathan Leivent" <jlei...@comcast.net>, erlang-q...@erlang.org
> Sent: Monday, 16 December, 2013 12:00:35 PM
> Subject: Re: [erlang-questions] style question - best way to test multiple non-guard conditions sequentially
>

Anthony Ramine

unread,
Dec 26, 2013, 4:47:11 AM12/26/13
to Robert Virding, Jonathan Leivent, Erlang-Questions Questions
I dislike very much reusing a token for something different (in that case ‘=‘).

Cond expressions should just not be nested cases sugar.

--
Anthony Ramine
Reply all
Reply to author
Forward
0 new messages