“模式匹配”代数类型数据构造函数
让我们考虑一个具有许多构造函数的数据类型:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
另一种可能的方法:
运行时错误并不理想,但比默默给出错误答案要好。
Another possible way:
A runtime error is not ideal, but better than silently giving the wrong answer.
查看 Data.Data 模块,特别是
toConstr
函数。与{-# LANGUAGE DeriveDataTypeable #-}
一起,您将获得一个 1 行解决方案,适用于作为Data.Data
实例的任何类型。您无需了解 SYB 的全部内容!如果由于某种原因(坚持拥抱?),这不是一个选择,那么这里有一个非常丑陋且非常缓慢的黑客攻击。仅当您的数据类型是
Show
able(例如,通过使用deriving (Show)
- 这意味着内部没有函数类型)时,它才有效。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 ofData.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
Show
able (e.g. by usingderiving (Show)
- which means no function types inside, for example).constrT
gets the string representation of the outermost constructor of aT
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:
data T2 = Eta Int | T2 :^: T2
)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!
一般来说,您需要使用像 Scrap Your Boilerplate 或 uniplate 这样的泛型库来执行此操作。
如果您不想如此严厉,您可以使用 Dave Hinton 的解决方案以及空记录快捷方式:
这样您就不必知道每个构造函数有多少个参数。但显然仍有一些不足之处。
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:
So you don't have to know how many args each constructor has. But it obviously still leaves something to be desired.
在某些情况下,“废弃你的样板”库会有所帮助。
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
您绝对可以使用泛型来消除样板文件。您的代码是教科书示例,为什么我(和许多其他人从不在顶层使用
_
通配符)。虽然写出所有案例很乏味,但比处理错误要简单得多。在这个令人高兴的示例中,我不仅会使用 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
.