I've been reading this http://www.haskell.org/haskellwiki/Hask. I'm struggling with this part..
undef1 = undefined :: a -> b
undef2 = \_ -> undefined
And why they behave like this..
seq undef1 () = undefined
seq undef2 () = ()
undef2 () = undefined
What is the reason for this? I would like to understand this behaviour but I don't even know where to begin. In particular, why does undef2 behave differently under strict evaluation?
The special function seq
evaluates its first argument to weak head normal form and then returns its second argument. A good explanation of WHNF can be found here, but for the purposes of this answer I'll just use the Haskell wiki definition:
An expression is in weak head normal form (WHNF) if it's either:
- a constructor (eventually applied to arguments) like True, Just (square 42) or (:) 1
- a function applied to too few arguments (perhaps none) like (+) 2 or sqrt.
- or a lambda abstraction \x -> expression.
An important point is that when an expression is a constructor, seq
only looks at the constructor's tag. Thus, seq (Just undefined) 1
evaluates to 1
.
Another important point is that all types in Haskell are lifted - that is, evaluating a value of the type can lead to an infinite loop being executed or an exception being thrown (usually with error
or undefined
). After we've evaluated seq a b
, we can be sure that evaluating a
to WHNF won't lead to an infinite loop or an exception.
Armed with this knowledge, let's look at your example:
undef1 = undefined :: a -> b
undef2 = \_ -> undefined
When seq undef1 ()
is evaluated, seq
first tries to find out which of the three categories above undef1
belongs to. But undef1
is undefined
, and so the whole expression evaluates to undefined
.
However, in the case of seq undef2 () = ()
, the first argument is a lambda abstraction. Since seq
can't see past the lambda, it returns the second argument.
The third example, undef2 () = undefined
, is just a straightforward result of evaluating the application (\_ -> undefined) ()
.
They're not the same thing. undef1 is a function from a to b, but which function is undefined. Evaluating undef1 to head normal form gives you undefined.
undef2 is a function from a to b. Specifically, it is a function that ignores its argument and returns undefined. But undef2 isn't undefined itself. Only when you try to evaluate the function (as in your third line) you get undefined. So when you evaluate undef2 to head normal form, you get a proper function, not undefined.
To put it into more imperative terms (always a source for inaccuracy, but if you're more familiar with that, it illustrates the point nicely), think of undef1 as a property getter that never returns. (In Haskell, not returning and undefined are semantically equivalent.) undef2 is a property getter that returns a function; it's that function that won't return if you call it. In C#:
Func<Object, Object> Undef1 {
get {
while (true) {}
return null;
}
}
Func<Object, Object> Undef2 {
get {
return _ -> {
while (true) {}
return null;
}
}
}
Now your tests become:
var x = Undef1;
var y = Undef2;
var z = Undef2(null);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With