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

sas*_*nin 13 haskell types pattern-matching algebraic-data-types

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

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int
Run Code Online (Sandbox Code Playgroud)

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

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False
Run Code Online (Sandbox Code Playgroud)

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

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True
Run Code Online (Sandbox Code Playgroud)

问题是如何避免样板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
Run Code Online (Sandbox Code Playgroud)

dav*_*420 14

另一种可能方式:

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

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

  • 我喜欢.带有`-Wall`的GHC在`f'的定义中报告非执行模式匹配,因此可以防止运行时错误.谢谢你的想法. (3认同)

luq*_*qui 10

您需要使用诸如Scrap Your Boilerplate之类的泛型库或通常使用uniplate来执行此操作.

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

...
where f (Alpha {}) = 0
      f (Beta {}) = 1
      f (Gamma {}) = 2
Run Code Online (Sandbox Code Playgroud)

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

  • 用`{}`很好的把戏.我不知道.谢谢. (2认同)
  • 记录括号绑定比其他任何东西都强,所以从技术上讲,你不需要括号.`f Alpha {} = 0`可以正常工作,虽然我不确定它的可读性,看起来像`f`有两个参数.我有时会涉及`f Alpha {} = 0` ...... (2认同)

yat*_*975 10

查看Data.Data模块,toConstr特别是该函数.随之而来的{-# LANGUAGE DeriveDataTypeable #-}将是一个1行解决方案,适用于任何类型的实例Data.Data.你不需要弄清楚所有的SYB!

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

constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y
Run Code Online (Sandbox Code Playgroud)

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

一些值得注意的缺点:

  • 当你的类型有中缀构造函数(例如data T2 = Eta Int | T2 :^: T2)时,这会破坏性地破坏
  • 如果你的一些构造函数有一个共享前缀,这将变得更慢,因为必须比较更大的字符串部分.
  • 不适用于具有自定义的类型show,例如许多库类型.

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