Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up(||) and (&&) not short-circuiting #495
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Jan 30, 2016
Contributor
Rationale for abolishing Basics.(||) and Basics.(&&) as functions (but keeping || and && as expression syntax, of course):
It will prevent people from accidentally writing inefficient code. For example, someone might currently cleverly write something like: oldWay = higherOrderFunction (flip (||) expensiveExpression). If (||) is removed as function, that someone would have to write newWay = higherOrderFunction (\arg -> arg || expensiveExpression) instead. Which is good, because newWay is much better than oldWay when higherOrderFunction applies its argument function to an expression that evaluates to True.
Can anybody show me, in Elm, a good use of (||) or (&&)? For example, there is good reason why List.any is not implemented as
any p = foldr ((||) << p) FalseIn Haskell, yes, but in Elm, no. So let's get rid of (||) and (&&).
|
Rationale for abolishing It will prevent people from accidentally writing inefficient code. For example, someone might currently cleverly write something like: Can anybody show me, in Elm, a good use of any p = foldr ((||) << p) FalseIn Haskell, yes, but in Elm, no. So let's get rid of |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mapmarkus
Feb 9, 2016
I've been exploring Elm's internals lately, and the behavior you describe might be the expected behavior given the underlying details (not to confused with the desired behavior).
Elm aliases operators like || or && to its native representation in javascript, so they keep their short-circuiting capabilities.
However, if you turn an operator into a function, elm compiler creates a wrapper around the operator, something like:
function (a, b) { return a || b; }and because neither Elm nor javascript are lazy, the parameter b is evaluated before being sent to the
operator (hence the behavior you observe).
In Haskell however, this will not happen because, Haskell being lazy, the parameter b would not be "looked at" unless its value is necessary.
We could devise a general solution, internally, using thunks when operators are used as functions, like
function (_a, _b) { return _a() || _b(); }But honestly, this might be one of those things that are better solved by just adding a warning to the compiler that fires when certain operators are used as functions.
mapmarkus
commented
Feb 9, 2016
|
I've been exploring Elm's internals lately, and the behavior you describe might be the expected behavior given the underlying details (not to confused with the desired behavior). Elm aliases operators like However, if you turn an operator into a function, elm compiler creates a wrapper around the operator, something like: function (a, b) { return a || b; }and because neither Elm nor javascript are lazy, the parameter In Haskell however, this will not happen because, Haskell being lazy, the parameter We could devise a general solution, internally, using thunks when operators are used as functions, like function (_a, _b) { return _a() || _b(); }But honestly, this might be one of those things that are better solved by just adding a warning to the compiler that fires when certain operators are used as functions. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Feb 9, 2016
Contributor
I know why it is the way it is, functions being strict in Elm (but operators as syntax being compiled differently). That doesn't change the fact that the Basics module documentation of the functions currently raises different (wrong) expectations on the programmer's side. It also doesn't change the fact that I don't know of any good uses of the functions in Elm. Do you? If there are none, then the best solution is not to throw warnings when the operators are used as functions, but instead to not provide them as functions.
|
I know why it is the way it is, functions being strict in Elm (but operators as syntax being compiled differently). That doesn't change the fact that the Basics module documentation of the functions currently raises different (wrong) expectations on the programmer's side. It also doesn't change the fact that I don't know of any good uses of the functions in Elm. Do you? If there are none, then the best solution is not to throw warnings when the operators are used as functions, but instead to not provide them as functions. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mapmarkus
Feb 9, 2016
I don't have any good example to be honest, but I don't think we need any.
The key idea is that, in general, Elm allows any operator to be used as a function. What you appear to be suggesting is to change this to any operator except || and &&.... Isn't that bringing underlying implementation details (how javascript works) into Elm's syntax (to the programmer side, as you say)? I though Elm, as a language, aimed to be as independent of the underlying javascript as possible.
mapmarkus
commented
Feb 9, 2016
|
I don't have any good example to be honest, but I don't think we need any. The key idea is that, in general, Elm allows any operator to be used as a function. What you appear to be suggesting is to change this to any operator except |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Feb 9, 2016
Contributor
It's not bringing JS semantics into the picture, it's being true to Elm's semantics. In Elm all operators are strict, except those two. So we should acknowledge that these two are not functions, they are syntax.
Another take on this: What about if-then-else in Elm? It's currently only available as a syntactic operator, not as a function. By your "there should be no exceptions rule", there should be a function version of if-then-else. There isn't. I think that && and || should be treated like if-then-else, not like + and -. Because everything else is a "lie" (like the current documentation of (&&) and (||) in the Basics module is a lie).
|
It's not bringing JS semantics into the picture, it's being true to Elm's semantics. In Elm all operators are strict, except those two. So we should acknowledge that these two are not functions, they are syntax. Another take on this: What about |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Feb 9, 2016
Contributor
Of course, any programmer will still be able to define a function like
or x y = x || yBut then the semantics will be clear, and that programmer's responsibility, and they will not be able (and not tempted) to claim that these are short-circuiting, like currently wrongly claimed about (&&) and (||).
|
Of course, any programmer will still be able to define a function like or x y = x || yBut then the semantics will be clear, and that programmer's responsibility, and they will not be able (and not tempted) to claim that these are short-circuiting, like currently wrongly claimed about |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mapmarkus
Feb 9, 2016
Well, I don't think if then else qualifies as an operator. And the rule is not mine at all, is just my interpretation of how Elm's handles operators turned into functions. I might be totally wrong, of course.
mapmarkus
commented
Feb 9, 2016
|
Well, I don't think |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Feb 9, 2016
Contributor
About the rule, yeah sure.
About if-then-else not being an operator, why not? Because it is ternary?
Anyway, I can rephrase my stance without mentioning operators: For me, && and || are not functions, they are syntax; just like if-then-else is not a function but syntax. So what is the reason that && and || must be available as functions?
|
About the rule, yeah sure. About Anyway, I can rephrase my stance without mentioning operators: For me, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
rebelwarrior
Apr 7, 2016
Contributor
Another way of solving it is having && and || not have direct function equivalents but have Basics.or and Basics.and to exist as functions analogues. So you would know they are not the same thing.
However, I agree with @jvoigtlaender that Basics.(&&) and Basics.(||) should be removed as methods (functions) from core if they don't have short circuit capabilities because that's what I'd expect and desire from those methods (functions).
|
Another way of solving it is having However, I agree with @jvoigtlaender that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
altaic
Apr 7, 2016
Yikes! Short-circuiting is one of the most basic optimizations that is assumed to work in pretty much any language I've seen. This should either be fixed by using thunks (in which case the JS JIT might optimize the thunks away), or by writing an optimizer pass, or ditched altogether. Adding a warning only makes Elm seem flawed, which, to a new Elm developer, could be reason enough to walk away.
altaic
commented
Apr 7, 2016
|
Yikes! Short-circuiting is one of the most basic optimizations that is assumed to work in pretty much any language I've seen. This should either be fixed by using thunks (in which case the JS JIT might optimize the thunks away), or by writing an optimizer pass, or ditched altogether. Adding a warning only makes Elm seem flawed, which, to a new Elm developer, could be reason enough to walk away. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
evancz
May 11, 2016
Member
I don't think this is a mistake. All infix operators can be used as functions. Functions evaluate all their arguments before they are called.
Certain infix operators have short-circuiting. They are not functions, so the same rules do not apply.
I'm fine with how it is.
|
I don't think this is a mistake. All infix operators can be used as functions. Functions evaluate all their arguments before they are called. Certain infix operators have short-circuiting. They are not functions, so the same rules do not apply. I'm fine with how it is. |
evancz
closed this
May 11, 2016
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
May 11, 2016
Contributor
But maybe at least a heads up in the documentation in Basics would be in order? That documentation currently reads like this:
(&&) : Bool -> Bool -> BoolThe logical AND operator.
Trueif both inputs areTrue. This operator short-circuits toFalseif the first argument isFalse.
It says "this operator", and above that it is written "(&&)". So that's "this operator", right? Someone learning Elm has no reason to not assume from this that (&&) False exp will short-circuit. Or that passing "the operator" (&&) to another function will still provide short-circuiting. In your reply above you make a difference between operators and functions, and are of course correct about it, but people learning Elm will not necessarily be aware of this distinction, and specifically not here, where "this operator" refers to (&&) - which in your reply isn't actually the operator but the function.
I haven't made this problem up. It originally came up in a student's code. I can elaborate on what the problem was and why none of the current language and core documentation addressed it. But maybe I'll instead first make a more modest documentation change proposal than what I started this issue with. Maybe you'll like that proposal without further ado. Maybe not.
I propose to change the documentation to:
(&&) : Bool -> Bool -> BoolThe logical AND operator.
Trueif both inputs areTrue. This operator, when used infix, short-circuits toFalseif the first argument isFalse.
So only adding the words "when used infix".
|
But maybe at least a heads up in the documentation in
It says "this operator", and above that it is written " I haven't made this problem up. It originally came up in a student's code. I can elaborate on what the problem was and why none of the current language and I propose to change the documentation to:
So only adding the words "when used infix". |
jvoigtlaender commentedJan 30, 2016
One might expect that there is no difference in behavior between this program:
and this program:
But there is. The first program shows a result immediately, whereas the second program goes into a near-endless computation (the browser complains and wants to stop the script).
An analogous problem exists for
(&&).I think the documentation should warn about that. Maybe at several places (the syntax guide, when talking about operators; the
coredocumentation).It might even be worth considering to remove
Basics.(||)andBasics.(&&)altogether, and only leave the||and&&in as pieces that the compiler treats specially. (Apparently the compiler currently does treat them specially? Or how else is it explained that the expressionTrue || fib 100 > 0does not fire off that expensivefib 100call?)