haskellpattern-matchingrational-numberrational-numbers

Pattern-matching on Rationals in Haskell


The following function is pretty straightforward:

test :: Int -> Int
test x = case x of
    0 -> 0
    1 -> 1
    _ -> 2

and indeed, test 0 == 0, test 1 == 1, and test 77 == 2.

The following function is almost as straightforward:

import Data.Ratio

test2 :: Rational -> Int
test2 = case x of
    0 -> 0
    1 % 2 -> 1
    _ -> 2

Loading this code in GHCi gives an error Parse error in pattern: 1 % 2.

What gives? Why can't I pattern-match on rational numbers? I can solve the real-world problem this example came from with guards, but I'm curious why pattern-matching doesn't work.


Solution

  • You can in general not pattern match on functions. That would require computing the inverse, which usually doesn't even exist. You can only match on constructors like Just or :+: these are recognisable from ordinary functions / infix operators by starting with an uppercase character or a colon.

    You can pattern match on rationals.

    import GHC.Real (:%)
    
    test2 :: Rational -> Int
    test2 = case x of
        0 -> 0
        1 :% 2 -> 1
        _ -> 2
    

    The reason, I suppose, why it's not really recommended to use :% (and it's hence only exported from an internal module, not from Data.Ratio) is that Ratio values are always supposed to be minimal, but :% as a plain constructor doesn't ensure this:

    Prelude Data.Ratio GHC.Real> 4%2
    2 % 1
    Prelude Data.Ratio GHC.Real> 4:%2
    4 % 2
    

    In particular, if you'd then actually pattern-match on such an unnormalised fraction, you couldn't be sure to succeed.

    In cases like 1%2, you can circumvent the problem by pattern matching on a decimal fraction (finite decimal fractions are unique):

    test2 :: Rational -> Int
    test2 = case x of
        0   -> 0
        0.5 -> 1
        _   -> 2
    

    Of course, this is perhaps not that nice. In modern Haskell, one could theoretically re-define :% as a smart pattern synonym:

    {-# LANGUAGE PatternSynonyms, ViewPatterns #-}
    import Data.Ratio
    
    numDenum :: Integral a => Ratio a -> (a,a)
    numDenum x = (numerator x, denominator x)
    
    pattern (:%) :: () => Integral a => a -> a -> Ratio a
    pattern a:%b <- (numDenum -> (a,b))
     where a:%b = a%b
    

    which could then be used as in your original example.

    ... but frankly, it's probably better to just use numerator and denominatoras they are.