Closed jvoigtlaender closed 8 years ago
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) False
In Haskell, yes, but in Elm, no. So let's get rid of (||)
and (&&)
.
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.
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 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.
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).
Of course, any programmer will still be able to define a function like
or x y = x || y
But 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 (||)
.
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.
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?
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).
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.
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.
But maybe at least a heads up in the documentation in Basics
would be in order? That documentation currently reads like this:
(&&) : Bool -> Bool -> Bool
The logical AND operator.
True
if both inputs areTrue
. This operator short-circuits toFalse
if 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 -> Bool
The logical AND operator.
True
if both inputs areTrue
. This operator, when used infix, short-circuits toFalse
if the first argument isFalse
.
So only adding the words "when used infix".
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
core
documentation).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 > 0
does not fire off that expensivefib 100
call?)