Consider this (rather cut-down) function for catching the failure of pattern matching.
module Handle where
import Control.Exception
import System.IO.Unsafe
wrap :: (a -> b) -> a -> b
wrap f x =
unsafePerformIO (catch (return $! (f x)) handler)
where
handler :: PatternMatchFail -> b
handler _ = error "caught"
I've put it in a separate module to stop it being inlined into the examples that follow. Here is an example of using the wrapper.
module Main where
import Handle
f [x] = x
g [x] y = x + y
h [x] = \ y -> x + y
main = do putStrLn (show (wrap h [] 4))
Let's try it with GHCi. As expected, in simple examples, wrap either returns the value of f x, or if the pattern fails, it traps the error:
mike@spinnaker:~$ ghci example.hs
GHCi, version 8.4.4: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling Handle ( Handle.hs, interpreted )
[2 of 2] Compiling Main ( example.hs, interpreted )
Ok, two modules loaded.
*Main> wrap f [3]
3
*Main> wrap f []
*** Exception: caught
But now consider the functions g and h, which ought to be equivalent to each other. The wrap function doesn't work with g, leaving the failure to be caught at the top level, but it does work with h.
*Main> wrap g [3] 4
7
*Main> wrap g [] 4
*** Exception: example.hs:6:1-15: Non-exhaustive patterns in function g
*Main> wrap h [] 4
*** Exception: caught
Now let's try GHC itself. The results are that, with optimisation turned off, the error from h is caught; but under -O the optimiser presumably turns h into g, and the error is no longer caught.
mike@spinnaker:~$ ghc -O --make -o example example.hs
mike@spinnaker:~$ example
example: example.hs:8:1-20: Non-exhaustive patterns in function h
I can well imagine that the optimiser turns the nested function h into a two-arguments-at-once function like g, and that applying g to a single argument yields an object that is considered a head-normal form until it receives its second argument, so the pattern matching failure is delayed until we are outside the scope of wrap. My questions are these: is this explanation broadly correct? And if we want consistent behaviour, how can we achieve it?
(The example is extracted from an interpreter for a higher-order programming langugage, where the partial functions are installed as primitives, and we want a uniform method of dealing with failure of primitives, whether they deliver a higher-order result or not: that varies according to the underlying monad.)