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_singleis_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,并仍然提供?

一种方法是使用DataTypeableData.Data模块。 这种方法依赖于两个自动生成的types实例,这些实例为您提供有关types的元数据: TypeableData 。 你可以用{-# 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) 

这依赖于FlexibleInstanceOverlappingInstancesUndecidableInstances ,所以它可能有点邪恶,但是,使用着名的眼球定理,应该没问题。 除非添加更多的实例,或者尝试将其用于不是构造函数的东西。 那么它可能会炸毁。 猛烈。 没有保证。

最后,在邪恶整齐地包含的情况下,我们可以写一个“相等的构造函数”操作符:

 (=|=) :: (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类。