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
4

5 回答 5

15

另一种可能的方式:

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

运行时错误并不理想,但比默默地给出错误答案要好。

于 2010-04-13T09:52:13.453 回答
10

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

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

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

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

于 2010-04-13T10:24:40.830 回答
10

查看 Data.Data 模块,toConstr尤其是函数。除此之外{-# LANGUAGE DeriveDataTypeable #-},您还将获得一个适用于任何类型的单行解决方案,即Data.Data. 你不需要弄清楚所有的SYB!

如果由于某种原因(卡住了拥抱?),这不是一个选择,那么这是一个非常丑陋且非常缓慢的 hack。它仅在您的数据类型Show能够使用时才有效(例如,通过使用deriving (Show)- 这意味着内部没有函数类型)。

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

constrTT通过显示一个值的最外层构造函数,将其分解为单词,然后获取第一个,来获取该值的最外层构造函数的字符串表示形式。我给出了一个明确的类型签名,所以你不会想在其他类型上使用它(并规避单态限制)。

一些显着的缺点:

  • 当您的类型具有中缀构造函数(例如data T2 = Eta Int | T2 :^: T2)时,这会严重中断
  • 如果你的一些构造函数有一个共享前缀,这会变慢,因为必须比较大部分字符串。
  • 不适用于具有 custom 的类型show,例如许多库类型。

也就是说,它Haskell 98 ......但这是我能说的唯一一件好事!

于 2010-04-13T14:26:56.987 回答
2

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

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

于 2010-04-13T08:47:56.053 回答
1

您绝对可以使用泛型来消除样板。您的代码是一个教科书示例,为什么我(和许多其他人从未_在顶层使用通配符)。虽然写出所有案例很乏味,但它比处理错误更乏味。

在这个快乐的例子中,我不仅会使用 Dave Hinton 的解决方案,还会在辅助函数上添加一个 INLINE pragma f

于 2010-04-14T01:12:39.303 回答