Haskell is indeed lazy. Laziness means that an expression is not evaluated unless required. However, laziness doesn't mean that two expressions can be evaluated in an arbitrary order. The order of evaluation of expressions in Haskell matters. For example, consider your und
function:
und :: Bool -> Bool -> Bool
und False y = False
und y False = False
First, I would like to note that this function is incomplete. The complete function is:
und :: Bool -> Bool -> Bool
und False y = False
und y False = False
und y True = True -- you forgot this case
In fact, the und
function can be written more succinctly (and more lazily) as follows:
-- if the first argument is False then the result is False
-- otherwise the result is the second argument
-- note that the second argument is never inspected
und :: Bool -> Bool -> Bool
und False _ = False
und _ x = x
Anyway, the pattern matching syntax in Haskell is just syntactic sugar for case
expressions. For example, your original (incomplete) function would be desugared to (up to alpha equivalence):
und :: Bool -> Bool -> Bool
und x y = case x of False -> False
True -> case y of False -> False
True -> undefined
From this we can see:
- Your function is incomplete because the last case is
undefined
.
- Your function evaluates the second argument if the first argument is
True
even though it doesn't need to. Remember that case
expressions always force the evaluation of the expression being inspected.
- Your function first inspects
x
and then inspects y
if x
evaluates to True
. Hence, there is indeed an explicit order of evaluation here. Note that if x
evaluates to False
then y
is never evaluated (proof that und
is indeed lazy).
It is because of this ordering of evaluation that your expression und (non_term 1) False
diverges:
und (non_term 1) False
= case non_term 1 of False -> False
True -> case False of False -> False
True -> undefined
= case non_term 2 of False -> False
True -> case False of False -> False
True -> undefined
= case non_term 3 of False -> False
True -> case False of False -> False
True -> undefined
.
.
.
.
If you want, you can create a function which has a different order of evaluation:
und :: Bool -> Bool -> Bool
und x y = case y of False -> False
True -> x -- note that x is never inspected
Now, the expression und (non_term 1) False
evaluates to False
. However, the expression und False (non_term 1)
still diverges. So, your main question is:
Is there a way to implement und
(i.e. and
in German) correctly (not just partially as above) so that both
und (non_term 1) False
and
und False (non_term 1)
return False?
The short answer is no. You always need a specific order of evaluation; and depending upon the order of evaluation either und (non_term 1) False
or und False (non_term 1)
will diverge.
Does this mean that Haskell is wrong/faulty? No. Haskell does the right thing and simply doesn't produce any answer. To a human (who can evaluate both expressions in parallel) it would seem apparent that the result of und (non_term 1) False
must be False
. However, computers must always have an order of evaluation.
So, what is the actual problem? In my humble opinion the actual problem is either/or:
Parallel evaluation. Haskell should evaluate the expression both ways in parallel and choose the one that terminates first:
import Data.Unamb (unamb)
type Endo a = a -> a
bidirectional :: Endo (a -> a -> b)
bidirectional = unamb <*> flip
und :: Bool -> Bool -> Bool
und = bidirectional (&&)
General recursion. In my humble opinion, general recursion is too powerful for most use cases: it allows you to write absurd functions like non_term x = non_term (x + 1)
. Such functions are quite useless. If we don't consider such useless functions as inputs then your original und
function is a perfectly good function to use (just implement the last case or use &&
).
Hope that helps.
False
is by opening the box, or evaluating the arguments. It can't possibly "guess" which order to do this in! – Petuliaunamb
, a parallel computation operator that says "run these two computations and use the result of the first one to terminate without an error." But basically, lazy evaluation is a specific evaluation strategy-there are others that may be better for your purposes. – Goveaunamb
... – Tonsil