Search code examples
haskelllazy-evaluation

Is pattern matching tuples not fully lazy?


I ran across some very unintuitive behavior in Haskell and am not sure if it is a bug in Haskell or not. If this is intended behavior, what specifically causes the infinite loop?

import Data.Function (fix)

main=putStrLn$show$fst$
    fix (\rec-> (\(a1,a2)->(3,7)) rec)

Infinite loops, I assume because to pattern match rec into rec (a1,a2) it evaluates. But I feel like it shouldn't need to because everything will match. For example these all work correctly:

    fix (\rec-> (\a->(3,7)) rec)
    fix (\rec-> (\a->let (a1,a2)=a in (3,7)) rec)

Interestingly this exits with error

    fix (\rec-> (\(a1,a2)->(3,7)) undefined)

But I could have sworn the original code where I encountered this behavior (which was more complicated) would actually work in this case. I could try to recreate it.


Solution

  • Correct, tuple pattern matches are not lazy. In fact, with two notable exceptions, no pattern match is lazy. To a first approximation, the "point" of pattern matching is to force evaluation of the scrutinee of the match.

    Of course, a pattern match may appear in a place where laziness means it is never performed; for example,

    const 3 (case loop of (_, _) -> "hi!")
    

    will not loop. But that's not because the match is lazy -- rather, it's because const is lazy and never causes the match to be performed. Your let example is similar, as the semantics of let is that variables bound by it are not forced before evaluating the body of the let.

    One of the two notable exceptions is a special pattern modifier, ~; putting ~ before a pattern says to make the match lazy and, consequently, is a claim to the compiler that the modified pattern always matches. (If it doesn't match, you get a crash once one of the bound variables is forced, not the usual fall-through-to-next-pattern behavior!) So, one fix for your fix is:

    fix (\rec -> (\ ~(a1, a2) -> (3, 7)) rec)
    

    Of course, the explicit name for rec isn't needed. You could just as well write:

    fix (\ ~(a1, a2) -> (3, 7))
    

    The other notable exception is newtype matching, which is not relevant here.