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

发布于 2024-08-28 11:27:23 字数 1208 浏览 14 评论 0原文

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

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

Let's consider a data type with many constructors:

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

I want to write a function to check if two values are produced with the same constructor:

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

Maintaining sameK is not much fun, it cannot be checked for correctness easily. For example, when new constructors are added to T, it's easy to forget to update sameK. I omitted one line to give an example:

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

The question is how to avoid boilerplate in sameK? Or how to make sure it checks for all T constructors?


The workaround I found is to use separate data types for each of the constructors, deriving Data.Typeable, and declaring a common type class, but I don't like this solution, because it is much less readable and otherwise just a simple algebraic type works for me:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

痴骨ら 2024-09-04 11:27:23

另一种可能的方法:

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

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

Another possible way:

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

A runtime error is not ideal, but better than silently giving the wrong answer.

热情消退 2024-09-04 11:27:23

查看 Data.Data 模块,特别是 toConstr 函数。与 {-# LANGUAGE DeriveDataTypeable #-} 一起,您将获得一个 1 行解决方案,适用于作为 Data.Data 实例的任何类型。您无需了解 SYB 的全部内容!

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

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

constrT 通过显示 T 值的最外层构造函数的字符串表示形式,将其分解为单词,然后获取第一个。我给出了一个显式的类型签名,这样您就不会想在其他类型上使用它(并逃避单态性限制)。

一些值得注意的缺点:

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

也就是说,它 Haskell 98...但这大概是我能说的关于它的唯一优点了!

Look at the Data.Data module, the toConstr function in particular. Along with {-# LANGUAGE DeriveDataTypeable #-} that will get you a 1-line solution which works for any type which is an instance of Data.Data. You don't need to figure out all of SYB!

If, for some reason (stuck with Hugs?), that is not an option, then here is a very ugly and very slow hack. It works only if your datatype is Showable (e.g. by using deriving (Show) - which means no function types inside, for example).

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

constrT gets the string representation of the outermost constructor of a T value by showing it, chopping it up into words and then getting the first. I give an explicit type signature so you're not tempted to use it on other types (and to evade the monomorphism restriction).

Some notable disadvantages:

  • This breaks horribly when your type has infix constructors (such as data T2 = Eta Int | T2 :^: T2)
  • If some of your constructors have a shared prefix, this is going to get slower, as a larger part of the strings has to be compared.
  • Doesn't work on types with a custom show, such as many library types.

That said, it is Haskell 98... but that's about the only nice thing I can say about it!

夏末染殇 2024-09-04 11:27:23

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

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

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

这样您就不必知道每个构造函数有多少个参数。但显然仍有一些不足之处。

You'll need to use a generics library like Scrap Your Boilerplate or uniplate to do this in general.

If you don't want to be so heavy-handed, you can use Dave Hinton's solution, together with the empty record shortcut:

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

So you don't have to know how many args each constructor has. But it obviously still leaves something to be desired.

遇见了你 2024-09-04 11:27:23

在某些情况下,“废弃你的样板”库会有所帮助。

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

In some cases, "Scrap Your Boilerplate" library will help.

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

别挽留 2024-09-04 11:27:23

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

在这个令人高兴的示例中,我不仅会使用 Dave Hinton 的解决方案,还会在辅助函数 f 上添加 INLINE pragma。

You can definitely use the generics to eliminate the boilerplate. Your code is a textbook example why I (and many others never use the _ wildcard at top level). While it is tedious to write out all the cases, it is less tedious than dealing with the bugs.

In this happy example I would not only use Dave Hinton's solution but would slap an INLINE pragma on the auxiliary function f.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文