首页 文章

代数型数据构造函数的“模式匹配”

提问于
浏览
13

让我们考虑一个包含许多构造函数的数据类型:

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int

我想编写一个函数来检查是否使用相同的构造函数生成了两个值:

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False

维护 sameK 并不是很有趣,无法轻易检查其正确性 . 例如,当将新构造函数添加到 T 时,很容易忘记更新 sameK . 我省略了一行来举个例子:

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True

问题是如何避免 sameK 中的样板?或者如何确保它检查所有 T 构造函数?


我找到的解决方法是为每个构造函数使用单独的数据类型,派生 Data.Typeable ,并声明一个公共类型类,但我不喜欢这个解决方案,因为它的可读性要低得多,否则只需要一个简单的代数类型就可以了我:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

class Tlike t where
  value :: t -> t
  value = id

data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable

instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b

5 回答

  • 14

    你绝对可以使用泛型来消除样板 . 您的代码是一个教科书示例,为什么我(以及许多其他人从未在顶级使用 _ 通配符) . 尽管写出所有案例都很繁琐,但与处理错误相比,这样做更为繁琐 .

    在这个快乐的例子中,我不仅会使用Dave Hinton的解决方案,而且还会在辅助函数 f 上使用INLINE编译指示 .

  • 2

    查看Data.Data模块,特别是 toConstr 函数 . 与 {-# LANGUAGE DeriveDataTypeable #-} 一起,它将为您提供一个解决方案,适用于任何类型的 Data.Data 实例 . 你不需要弄清楚所有的SYB!

    如果,由于某种原因(坚持使用Hugs?),这不是一个选项,那么这是一个非常丑陋且非常缓慢的黑客攻击 . 它仅在您的数据类型为 Show 能时才有效(例如,通过使用 deriving (Show) - 这意味着内部没有函数类型) .

    constrT :: T -> String
    constrT = head . words . show
    sameK x y = constrT x == constrT y
    

    constrT 通过显示它来获取 T 值的最外层构造函数的字符串表示形式,将其切割成单词然后获取第一个 . 我给出了一个显式的类型签名,所以你不想在其他类型上使用它(并避免单态限制) .

    Some notable disadvantages:

    • 当你的类型有中缀构造函数(例如 data T2 = Eta Int | T2 :^: T2 )时,这会破坏性的

    • 如果你的一些构造函数有一个共享前缀,这将变得更慢,因为必须比较大部分字符串 .

    • 不适用于具有自定义 show 的类型,例如许多库类型 .

    也就是说,它是Haskell 98 ......但这是关于它的唯一好处!

  • 10

    另一种可能方式:

    sameK x y = f x == f y
      where f (Alpha _)   = 0
            f (Beta _)    = 1
            f (Gamma _ _) = 2
            -- runtime error when Delta value encountered
    

    运行时错误并不理想,但比默默地给出错误的答案更好 .

  • 1

    您需要使用像Scrap Your Boilerplate这样的泛型库或uniplate来执行此操作 .

    如果你不想如此苛刻,你可以使用Dave Hinton的解决方案,以及空记录快捷方式:

    ...
    where f (Alpha {}) = 0
          f (Beta {}) = 1
          f (Gamma {}) = 2
    

    所以你不必知道每个构造函数有多少个args . 但它显然仍有一些不足之处 .

  • 10

    在某些情况下,“Scrap Your Boilerplate”库将有所帮助 .

    http://www.haskell.org/haskellwiki/Scrap_your_boilerplate

相关问题