3

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.

dvitek
  • 568
  • 6
  • 16

2 Answers2

7

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.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • This is neat as heck, and I'll probably accept it. But it doesn't quite answer the question of why I can't pattern-match on (%). I'm not particularly familiar with the internals of pattern-matching, so it may be that there's a simple answer I'm not seeing. – dvitek Aug 18 '16 at 07:54
  • Right, I added that to the top. – leftaroundabout Aug 18 '16 at 07:59
  • Oh, I see. I guess I didn't realize that `(%)` wasn't actually a constructor, but you can just look at its `:info` and it's clearly not one. The finite-decimal-fractions approach is a clever use of the default `Fractional a` type for decimals, which I'd forgotten about. I'm not sure I quite understand the smart pattern synonym answer - I use dinosaur Haskell - but it looks like I've got some reading to do. Thanks a bunch for the amazing answer! – dvitek Aug 18 '16 at 08:11
0

You can also use guards to do a very similar thing. You can use arbitrary Bool expressions, so you have the (%) and every other pure function available.

test3 :: Rational -> Int
test3 x | x == 0 = 0
        | x == 1 % 2 = 1
        | otherwise = 2

They work in a case statement too.

test3a :: Rational -> Int
test3a y = case y of
    x | x == 0 -> 0
      | x == 1 % 2 -> 1
      | otherwise -> 2
NovaDenizen
  • 5,089
  • 14
  • 28