简体   繁体   中英

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.

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 denominator as they are.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM