how does 'undefined' work in Haskell

后端 未结 6 1402
广开言路
广开言路 2021-02-05 04:23

I\'m curious about the \'undefined\' value in Haskell. Its interesting because you can put it just about anywhere, and Haskell will be happy. The following are all a-ok

6条回答
  •  逝去的感伤
    2021-02-05 04:36

    The interesting property you're examining is that undefined has the type a for any type a we choose, i.e. undefined :: a with no constraints. As others have noted, undefined might be thought of as an error or infinite loop. I'd like to argue that it's better thought of as "the vacuously true statement". It's an almost unavoidable hole in any type system closely related to the Halting Problem, but it's fun to think about it from the point of view of logic.


    One way to think about programming with types is that it's a puzzle. Someone gives you a type and asks you to implement a function which is of that type. For instance

    Question:    fn  ::  a -> a
    Answer:      fn  =  \x -> x
    

    is an easy one. We need to produce an a for any type a, but we're given one as an input so we can just return that.

    With undefined, this game is always easy

    Question:    fn  ::  Int -> m (f [a])
    Answer:      fn  =  \i   -> undefined    -- backdoor!
    

    so let's be rid of it. Making sense of undefined is easiest when you live in a world without it. Now our game gets harder. Sometimes it's possible

    Question:    fn  :: (forall r. (a -> r) -> r) -> a
    Answer:      fn  =  \f                        -> f id
    

    But suddenly it's sometimes not possible as well!

    Question:    fn  ::  a -> b
    Answer:      fn  =   ???                  -- this is `unsafeCoerce`, btw.
                                              -- if `undefined` isn't fair game,
                                              -- then `unsafeCoerce` isn't either
    

    Or is it?

    -- The fixed-point combinator, the genesis of any recursive program
    
    Question:    fix  ::  (a -> a) -> a
    Answer:      fix  =   \f       -> let a = f a in a
    
                                              -- Why does this work?
                                              -- One should be thinking of Russell's 
                                              -- Paradox right about now. This plays
                                              -- the same role as a non-wellfounded set.
    

    Which is legal because Haskell's let binding is lazy and (generally) recursive. Now we're golden.

    Question:    fn   ::  a -> b
    Answer:      fn   =  \a -> fix id         -- This seems unfair?
    

    Even without having undefined built-in we can rebuild it in our game using any old infinite loop. The types check out. To truly prevent ourselves from having undefined in Haskell we'd need to solve the Halting Problem.

    Question:    undefined  ::  a
    Answer:      undefined  =   fix id
    

    Now, as you've seen, undefined is useful for debugging since it can be a placeholder for any value. It's unfortunately terrible for operations since it either leads to an infinite loop or an immediate crash. It's also really bad for our game because it lets us cheat. Finally, I hope I've demonstrated that it's pretty hard to not have undefined so long as your language has (potentially infinite) loops.

    There exist languages like Agda and Coq which trade away loops in order to truly eliminate undefined. They do this because this game I've invented can, in some cases, actually be very valuable. It can encode statements of logic and thus be used to form very, very rigorous mathematical proofs. Your types represent theorems and your programs are assurances that that theorem is substantiated. The existence of undefined would mean that all theorems are substantiatable and thus make the whole system untrustworthy.

    But in Haskell, we're more interested in looping than proofing, so we'd rather have fix than be certain there wasn't an undefined.

提交回复
热议问题