Recently a PR deprecating and and or boolean operator made its way into OTP29. I would argue that these deprected operators have a legit (albeit limited) purpose, when one needs side effects of all function calls in a boolean expression. The counterpoint to that argument was that the compiler might re-order the calls in or and and. I wonder whether that is a theoretical possibility or real behavior of the compiler.
If it’s merely a theoretical possibility, and the compiler doesn’t attempt to “optimize” scheduling of the calls in a boolean expression (seems doubtful, as such reordering cannot yield any measurable benefit for eager operators, and it would be completely wrong for the lazy ones), then I propose to document that the order is preserved. That does not require any changes, other than the documentation. Warnings suggesting the use of short-circuiting boolean operators can still be emitted, but deliberate uses of eager boolean operators can be allowed in the future.
I’m not saying that your point is invalid but I think it’s worth mentioning that you do have a more explicit way to achieve your goal without eager operators…
You can turn this code:
something:with(side, effects) and something_else:with(side, effects) or other_thing:with(side, effects) ...
into…
SomethingMeaningful = something:with(side, effects),
SomehtingElseAlsoMeaningful = something_else:with(side, effects),
YetAnotherMeaningfulName = other_thing:with(side, effects),
....
SomethingMeaningful andalso SomethingElseAlsoMeaningful orelse YetAnotherMeaningfulName ...
Thanks, I know that.
The compiler does not re-order the arguments for or and and. However, it will rewrite them to the short-circuiting operators if the right-hand side expression doesn’t have any side effects.
Yes, that’s what we recommend doing to make it clear that there are side effects that should be evaluated in the given order.
The compiler does not re-order the arguments for
orandand. However, it will rewrite them to the short-circuiting operators if the right-hand side expression doesn’t have any side effects.
Thanks for the clarification, that is good to know.
Yes, that’s what we recommend doing to make it clear that there are side effects that should be evaluated in the given order.
I would argue that this approach has downsides too. I will not bring up conciseness and aesthetic arguments that
Ch1 = manage_priv(Context),
Ch2 = precondition([beam(Src, CRef) || Src <- FirstSources], 1),
Ch3 = precondition([beam(Src, CRef) || Src <- OtherSources]),
Ch4 = clean_orphans(Sources, Context),
Ch5 = copy_includes(Context),
Ch6 = render_app_spec(AppSrcProperties, Sources, Context),
AnyChanges = Ch0 orelse Ch1 orelse Ch2 orelse Ch3 orelse Ch4 orelse Ch5 orelse Ch6,
is uglier than
AnyChanges =
manage_priv(Context) or
precondition([beam(Src, CRef) || Src <- FirstSources], 1) or
precondition([beam(Src, CRef) || Src <- OtherSources]) or
clean_orphans(Sources, Context) or
copy_includes(Context) or
render_app_spec(AppSrcProperties, Sources, Context),
But instead I’ll make an argument that Erlang’s variable scoping rules make the first approach actually dangerous. Case in point:
Ch1 = manage_priv(Context),
Ch2 = precondition([beam(Src, CRef) || Src <- FirstSources], 1),
Ch2 = precondition([beam(Src, CRef) || Src <- OtherSources]),
Ch3 = clean_orphans(Sources, Context),
Ch4 = copy_includes(Context),
Ch5 = render_app_spec(AppSrcProperties, Sources, Context),
AnyChanges = Ch0 orelse Ch1 orelse Ch2 orelse Ch3 orelse Ch4 orelse Ch5,
The above code contains a simple copy-paste mistake. This code is syntactically correct, and it may even work most of the times. To spot accidental variable reuses, one needs a full semantic language server, which is not always available (e.g. Github code review). A seasoned Erlang programmer, is, of course, trained to spot these issues, but it increases mental load when reading the function regardless.
Hence, it’s a good idea to avoid polluting scope of a function with “temporary” variables.
And that’s exactly why I used ...Meaningful... names for my variables. ![]()
It’s harder to make this kind of copy-paste mistakes if you call your variables IsPrivManaged, FirstSourcesPassPrecondition, OtherSourcesPassPrecondition, NoOrphans, HasIncludes, AppSpecRendered… instead of Ch#.
Also… if all you have are orelses… then you can write the code with no boolean operator at all:
lists:any([IsPrivManaged, FirstSourcesPassPrecondition, …]).
…or, more to your style…
lists:any([
manage_priv(Context),
precondition([beam(Src, CRef) || Src <- FirstSources], 1),
…
]).
I’d like to keep this thread on the original topic, if this is ok with you. I didn’t create it because I hadn’t known about lists:any function or any other tricks I could employ to work around this new problem.
Niklaus Wirth was a brilliant, brilliant man But he made two horrible mistakes in Pascal: he gave and and or the.wrong precedence and he made them strict. I am very happy about this change.
Consider f(X,g(Y)) and g(Y).
This is a case where an optimising compiler would want to evaluate T=g(Y), f(X,T) and T.
But since Erlang functions cannot be presumed pure, thAt kind of optimisation won’t be done. My understanding is that in order to make it easier to understand exceptions, Erlang acts as if everything were done in left to right order.
There was a threadx in the old maing list to the effect that Boolean is often the wrong type. This means that complex combinations of and and or should be rare.
Y the way, has anyone adapted the interactive analysis tool Cobra to Erlang?
If you read the OP carefully, you’ll notice that I don’t want to take away your andalso and orelse operators. Nor do I propose to remove any warnings about eager operators.
Yes, that’s precisely the idea. There are cases when one doesn’t want such optimizations. This thread is about them.
You’re right, sorry. It was such a nice trick. I couldn’t contain myself.
I think that the rest of the text in my response was very much on point with your original topic, tho. The problem of producing errors by copy-pasting lines with variables that have similar names is a thing that’s generally solved by not using variables that have similar names
- Come to think of it, that’s also besides the point of your original topic, but… you opened that door, I guess.
Anyway, back to the original original thing: Yes, there were some pieces of code that were written using the (at that time valid) assumption that the expressions within eager boolean operators were evaluated sequentially, and then the boolean operations were evaluated.
These expressions were, crucially, not ignored when the deprecation of this operands was decided (as @bjorng points out).
Why, considering the existence of such expressions, were the operators still deprecated? Because there are arguably better ways to write the same code and it’s a good idea to encourage developers to use those.
Now, why is…
SomethingMeaningful = do:something(),
AnotherMeaningfulThing = do:something(else),
…
SomethingMeaningful orelse AnotherMeaningfulThing ...
…better code than…
do:something() or do:something(else) …
…?
Well, there might be other reasons but the one that stands out the most to me is Semantics, or rather “showing intention”.
I mean, clearly… the goal of that code is to do all those things in order and then return true if, at least, one of them returned true.
That translates neatly into Erlang code as such:
- do all those things in order : Several consecutive expressions separated by
,. - return
trueif, at least, one of [multiple things is]true: Eitherlists:any(MultipleThings)or several consecutive expressions separated byorelse.
While it’s true that the second expression produces the same result and has the same effects, the first one expresses its intent more clearly. Thus, at least in the absence of other external factors, the first one can be considered better code.
Hope this helps.
Now, why is… …better code than…
In your opinion. I get it. But, as I was saying, in my opinion this code is worse, because
- It adds single-use variables into the scope, and behavior of variables in Erlang is very much context-dependent. Because of that, I tend to optimize my functions to use less stay variables, because it makes their logic easier to follow.
- The two hardest problems in computer science are cache invalidation and naming things.
- “Copy-paste error” example was just the simplest demonstration, I could come up with something more complex, e.g. where “meaningful variable names” clash. I know, I know, “I must split functions into smaller functions with meaningful functions”, etc., etc. But I asked this question in a context of a certain tool that aims to replace shell scripts in some cases, and, as such, conciseness very much matters for its success.
So, let’s please not go further into it. That’s my style, it’s different from yours, you can like it or hate it; neither is worse, they’re just optimized for different metrics.
Hope this helps.
Yes, it certainly helped me to understand your coding style and your motivation behind it. Thank you. This thread was not about it, though.
I read the original post carefully. Nons of your responders supposes you want to do away with andor and orelse. That’s a straw man. The issue is that and and or are an “attractive nuisance”. Like an unfenced swimming pool. This change indtalls a safety fence.
I have a couple of Brian Marek’s “An Example would be Handy About Now” stickers. Let us see an actual example of code that is clearer using “and” and “or”. And let’s discuss how to improve it.
If you know my record, I’m normally pretty resistant to removing features / creating incompatibilities. And if you saw my Erlang code, you’d see very little use of Boolean operators because for so many reasons, Boolean is usually a bad choice.
I repeat my wuestion about Cobra.
It has been fenced, I think everyone is happy.
And if you saw my Erlang code
No, I don’t believe I have.
you’d see very little use of Boolean operators
So, a thread about boolean operators should not interest you then?
Why waste your time and mine on a topic so irrelevant.
I’m late to the party, but my main reason to use and/or rather than andalso/orelse is simply the case where I want this failure:
1> false or trueee.
** exception error: bad argument
in operator or/2
called as false or trueee
2> false orelse trueee.
trueee
As far as I can see, nobody mentioned this here, so I’m assuming I’m overlooking something.
Hi all, the thread has been cleaned up a bit.
Please remember in these types of threads it’s best to try and see things from everyone’s perspective - whether they are the proposer (why might they be proposing it? Why might it make sense? Could it actually be useful? Could it make Erlang better/more accessible? etc) as well as from those who are resistant or feel an alternative might exist (so long as they put their opinions across amicably). Finally, it’s always worth taking on board the viewpoints or reasoning of a core team member since they will have a very good insight into how the language is being used and how it works under the hood - sometimes, some things just aren’t a good fit or don’t feel right for the language or project the suggestion is being proposed for.
Ultimately the onus is on the proposer to convince the core team and everyone else their idea has merit and is worth pursuing, and generally that is done by engaging with anyone interested (or resistant) to the idea in an amicable manner because people are more attuned to your ideas that way (if you feel they are not taking part in good faith, just ignore them (or report them if you feel they have broken a rule) ..their actions are ultimately a reflection on them just as yours are on you).
With that said perhaps it’s best to let the thread wind down now.
@ieQu1, since you started the thread you can choose how to proceed:
- Leave it open and on the public forum
- Unlist it (or unlist and lock it)
- Move it to members-only
Just PM me your preference. If I don’t hear from you I’ll assume you’re happy with option 1.
This is something that the Dialyzer can help with.
If you’re talking about @holger’s ... orelse trueee, well… It’s something that Dialyzer could help with but only after a change in OTP. Because, currently, orelse and andalso do not impose any types on their second argument
. In fact, I’m not even sure if the type of the first one is required to be boolean().
In any case, I think that tightening the types for andalso and orelse (and possibly making them fail like and and or do today in runtime) would be something better than keeping and and or just because of this. But… @holger does have a point, indeed.
Making andalso/orelse fail that way at runtime would be a breaking change, of course—no doubt there’s code that deliberately does check(Bool) andalso io:put_chars("it's true!").
As for Dialyzer, I’m all for static checks whenever possible, but it has its known limits with remote/replaced code etc., so runtime failures remain a crucial feature for me.
andalso/orelse were deliberately made to work that way. See https://www.erlang.org/doc/system/expressions.html#short-circuit-expressions (the info box at the end of the paragraph):
Before Erlang/OTP R13A,
Expr2was required to evaluate to a Boolean value, and as a consequence,andalsoandorelsewere not tail-recursive.
Dialyzer won’t catch this error, because Boolean orelse NotBoolean in itself is valid code with success type false | NotBoolean. Dialyzer might catch this error in the right context, but a lot of stars should align and the warning will never point at the right place.