假设我有这样的数据类型:
data NumCol = Empty |
Single Int |
Pair Int Int |
Lots [Int]
现在我希望从[NumCol]
过滤出与给定构造函数匹配的元素。我可以写一下,例如Pair
:
get_pairs :: [NumCol] -> [NumCol]
get_pairs = filter is_pair
where is_pair (Pair _ _) = True
is_pair _ = False
这有效,但它不是通用的。我必须为is_single
,is_lots
等编写单独的函数。
我希望我可以写:
get_pairs = filter (== Pair)
但这仅适用于不带参数的类型构造函数(即Empty
)。
所以问题是,如何编写一个带有值和构造函数的函数,并返回该值是否与构造函数匹配?
答案 0 :(得分:32)
至少get_pairs
本身可以通过使用列表推导来相对简单地定义:
get_pairs xs = [x | x@Pair {} <- xs]
对于匹配构造函数的更一般解决方案,您可以使用lens
包中的棱镜:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Lens.Extras (is)
data NumCol = Empty |
Single Int |
Pair Int Int |
Lots [Int]
-- Uses Template Haskell to create the Prisms _Empty, _Single, _Pair and _Lots
-- corresponding to your constructors
makePrisms ''NumCol
get_pairs :: [NumCol] -> [NumCol]
get_pairs = filter (is _Pair)
答案 1 :(得分:28)
标记工会的标签应该是一流的价值观,并且经过一番努力,它们就是。
Jiggery-pokery警报:
{-# LANGUAGE GADTs, DataKinds, KindSignatures,
TypeFamilies, PolyKinds, FlexibleInstances,
PatternSynonyms
#-}
第一步:定义标签的类型级版本。
data TagType = EmptyTag | SingleTag | PairTag | LotsTag
第二步:为类型级标签的可表示性定义价值级证人。 Richard Eisenberg的Singletons图书馆将为您完成此任务。我的意思是这样的:
data Tag :: TagType -> * where
EmptyT :: Tag EmptyTag
SingleT :: Tag SingleTag
PairT :: Tag PairTag
LotsT :: Tag LotsTag
现在我们可以说我们希望找到与给定标签相关联的内容。
type family Stuff (t :: TagType) :: * where
Stuff EmptyTag = ()
Stuff SingleTag = Int
Stuff PairTag = (Int, Int)
Stuff LotsTag = [Int]
所以我们可以重构你最初想到的类型
data NumCol :: * where
(:&) :: Tag t -> Stuff t -> NumCol
并使用PatternSynonyms
恢复您的行为:
pattern Empty = EmptyT :& ()
pattern Single i = SingleT :& i
pattern Pair i j = PairT :& (i, j)
pattern Lots is = LotsT :& is
所以发生的事情是NumCol
的每个构造函数都变成了一个标签,该标签由它的标记类型索引。也就是说,构造函数标签现在与其余数据分开存在,通过公共索引进行同步,以确保与标签关联的内容与标签本身匹配。
但我们可以单独讨论标签。
data Ex :: (k -> *) -> * where -- wish I could say newtype here
Witness :: p x -> Ex p
现在,Ex Tag
是&#34;运行时标记的类型,类型级别对应&#34;。它有一个Eq
实例
instance Eq (Ex Tag) where
Witness EmptyT == Witness EmptyT = True
Witness SingleT == Witness SingleT = True
Witness PairT == Witness PairT = True
Witness LotsT == Witness LotsT = True
_ == _ = False
此外,我们可以轻松提取NumCol
的标签。
numColTag :: NumCol -> Ex Tag
numColTag (n :& _) = Witness n
这使我们能够符合您的规范。
filter ((Witness PairT ==) . numColTag) :: [NumCol] -> [NumCol]
这提出了一个问题,即您的规范是否真正符合您的需求。重点是检测标签可以让您对该标签的内容有所期待。输出类型[NumCol]
并不公平,因为您知道自己只有对。
你怎么能收紧你的功能类型并仍然提供它?
答案 2 :(得分:8)
一种方法是使用DataTypeable
和Data.Data
模块。此方法依赖于两个自动生成的类型类实例,这些实例为您提供有关该类型的元数据:Typeable
和Data
。您可以使用{-# LANGUAGE DeriveDataTypeable #-}
data NumCol = Empty |
Single Int |
Pair Int Int |
Lots [Int] deriving (Typeable, Data)
现在我们有一个toConstr
函数,给定一个值,它给我们一个构造函数的表示:
toConstr :: Data a => a -> Constr
这使得compare two terms的构造函数变得容易。唯一剩下的问题是我们需要一个值来比较我们定义谓词的时间!我们总是可以使用undefined
创建一个虚拟值,但这有点难看:
is_pair x = toConstr x == toConstr (Pair undefined undefined)
所以我们要做的最后一件事是定义一个方便的小类来自动执行此操作。基本思想是在非函数值上调用toConstr
,并通过首先传递undefined
来递归任何函数。
class Constrable a where
constr :: a -> Constr
instance Data a => Constrable a where
constr = toConstr
instance Constrable a => Constrable (b -> a) where
constr f = constr (f undefined)
这取决于FlexibleInstance
,OverlappingInstances
和UndecidableInstances
,所以它可能有点邪恶,但是,使用(in)着名的眼球定理,它应该没问题。除非您添加更多实例或尝试将其用于不是构造函数的东西。然后它可能会炸毁。猛烈。没有承诺。
最后,在整齐地包含邪恶的情况下,我们可以写一个&#34;等于构造函数&#34;操作者:
(=|=) :: (Data a, Constrable b) => a -> b -> Bool
e =|= c = toConstr e == constr c
(=|=
运算符有点助记符,因为构造函数在语法上定义为|
。)
现在你几乎可以写出你想要的东西了!
filter (=|= Pair)
另外,也许你想要关闭单态限制。实际上,这里是我启用的扩展列表,您可以使用:
{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, NoMonomorphismRestriction, OverlappingInstances, UndecidableInstances #-}
是的,它经常发生。但那就是我愿意为这个事业牺牲的东西。不写额外的undefined
s。
老实说,如果你不介意依赖lens
(但男孩是那种依赖性的话),你应该采用棱镜方法。我唯一要推荐的是你可以使用有趣的Data.Data.Data
类。