Haskell 中有理数的模式匹配

Pattern-matching on Rationals in Haskell

以下函数非常简单:

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

事实上,test 0 == 0test 1 == 1test 77 == 2

下面的函数几乎一样简单:

import Data.Ratio

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

在 GHCi 中加载此代码会出现错误 Parse error in pattern: 1 % 2

什么给?为什么我不能对有理数进行模式匹配?我可以解决这个例子中警卫带来的现实问题,但我很好奇为什么模式匹配不起作用。

您通常可以不对函数进行模式匹配。那将需要计算倒数,这通常甚至不存在。您只能匹配构造函数,例如Just or :+:这些可以通过以大写字符或冒号开头的普通函数/中缀运算符来识别。

可以 有理数模式匹配。

import GHC.Real (:%)

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

我想,为什么不真正推荐使用 :%(因此它只从内部模块导出,而不是从 Data.Ratio 导出)的原因是 Ratio 值总是应该是最小的,但是 :% 作为普通构造函数并不能确保这一点:

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

特别是,如果您真的对这样一个未标准化的分数进行模式匹配,您就不一定能成功。

在像1%2这样的情况下,您可以通过对小数进行模式匹配来规避问题(有限小数是唯一的):

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

当然,这可能不太好。在现代 Haskell 中,理论上可以将 :% 重新定义为智能模式同义词:

{-# 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

然后可以像在您的原始示例中那样使用。

...但坦率地说,直接使用 numeratordenominator 可能更好。

你也可以使用守卫来做非常相似的事情。您可以使用任意 Bool 表达式,因此您可以使用 (%) 和所有其他纯函数。

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

它们也适用于 case 语句。

test3a :: Rational -> Int
test3a y = case y of
    x | x == 0 -> 0
      | x == 1 % 2 -> 1
      | otherwise -> 2