testing一个值是否与构造函数匹配
说我有这样的数据types:
data NumCol = Empty | Single Int | Pair Int Int | Lots [Int]
现在我想从[NumCol]
过滤出与给定构造函数匹配的元素。 我可以写下来,比方说:
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)
但是这只适用于不带参数的types构造函数(即Empty
)。
所以问题是,我怎么能写一个函数,它接受一个值和一个构造函数,并返回值是否与构造函数匹配?
至lessget_pairs
本身可以通过使用列表理解来相对简单地进行定义:
get_pairs xs = [x | x@Pair {} <- xs]
对于匹配构造函数的更一般的解决scheme,您可以使用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)
有标记的工会的标签应该是一stream的价值观,只需要一点努力,他们就是。
Jiggery-pokery警报:
{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeFamilies, PolyKinds, FlexibleInstances, PatternSynonyms #-}
第一步:定义标签的types级版本。
data TagType = EmptyTag | SingleTag | PairTag | LotsTag
第二步:为types级别标签的可表示性定义值级目击者。 理查德·艾森伯格的单身人士图书馆将为你做这件事。 我的意思是这样的:
data Tag :: TagType -> * where EmptyT :: Tag EmptyTag SingleT :: Tag SingleTag PairT :: Tag PairTag LotsT :: Tag LotsTag
现在我们可以说出我们期望find的与给定标签相关联的东西。
type family Stuff (t :: TagType) :: * where Stuff EmptyTag = () Stuff SingleTag = Int Stuff PairTag = (Int, Int) Stuff LotsTag = [Int]
所以我们可以重构你首先想到的types
data NumCol :: * where (:&) :: Tag t -> Stuff t -> NumCol
并使用PatternSynonyms
来恢复你想到的行为:
pattern Empty = EmptyT :& () pattern Single i = SingleT :& i pattern Pair ij = PairT :& (i, j) pattern Lots is = LotsT :& is
所以发生的事情是, NumCol
每个构造NumCol
都变成了标签索引的标签。 也就是说,构造函数标签现在与其余的数据分开存储,并由一个通用索引进行同步,以确保与标签相关的内容与标签本身相匹配。
但是我们可以单独谈论标签。
data Ex :: (k -> *) -> * where -- wish I could say newtype here Witness :: px -> Ex p
现在,“ Ex Tag
”是“types级别对应的运行时标签”的types。 它有一个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]
这提出了你的规范是否真的是你需要的问题。 重点是检测一个标签赋予你一个期望的标签的东西。 输出types[NumCol]
不符合事实,你知道你只是对。
你如何收紧你的functiontypes,并仍然提供?
一种方法是使用DataTypeable
和Data.Data
模块。 这种方法依赖于两个自动生成的types实例,这些实例为您提供有关types的元数据: Typeable
和Data
。 你可以用{-# LANGUAGE DeriveDataTypeable #-}
来派生它们:
data NumCol = Empty | Single Int | Pair Int Int | Lots [Int] deriving (Typeable, Data)
现在我们有一个toConstr
函数,给它一个值,给它一个toConstr
函数的表示:
toConstr :: Data a => a -> Constr
这使得通过构造函数比较两个词很容易。 剩下的唯一问题是我们需要一个值来比较我们的谓词的定义! 我们总是可以创build一个undefined
的虚拟值,但这有点难看:
is_pair x = toConstr x == toConstr (Pair undefined undefined)
所以我们要做的最后一件事就是定义一个方便的小类来自动化这个。 其基本思想是在非函数值上调用toConstr
,并通过首先传入undefined
函数对任何函数进行recursion。
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
,所以它可能有点邪恶,但是,使用着名的眼球定理,应该没问题。 除非添加更多的实例,或者尝试将其用于不是构造函数的东西。 那么它可能会炸毁。 猛烈。 没有保证。
最后,在邪恶整齐地包含的情况下,我们可以写一个“相等的构造函数”操作符:
(=|=) :: (Data a, Constrable b) => a -> b -> Bool e =|= c = toConstr e == constr c
( =|=
运算符有点助记符,因为构造函数在语法上用|
定义)。
现在你几乎可以写出你想要的东西了!
filter (=|= Pair)
另外,也许你想closures单态限制。 事实上,下面是我可以使用的扩展名列表:
{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, NoMonomorphismRestriction, OverlappingInstances, UndecidableInstances #-}
是的,这很多。 但这就是我愿意为这个事业而牺牲的东西。 不写额外的undefined
的。
老实说,如果你不介意依靠lens
(但男孩是依赖的一个愚蠢的),你应该采用棱镜的方法。 唯一推荐我的是,你可以使用有趣的Data.Data.Data
类。