Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluating \_ -> undefined in Haskell

Tags:

haskell

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?

like image 288
user21154 Avatar asked Apr 16 '13 15:04

user21154


2 Answers

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) ().

like image 51
Mikhail Glushenkov Avatar answered Oct 05 '22 20:10

Mikhail Glushenkov


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);
like image 40
Sebastian Redl Avatar answered Oct 05 '22 19:10

Sebastian Redl