我粗略想要的是:
data A = ...
data B = ...
data C = ...
class HasA t where
getA :: t -> A
class HasB t where
getB :: t -> B
class HasC t where
getC :: t -> C
所以我可以这样做(伪代码如下):
a :: A
b :: B
x = mkRecord { elemA a, elemB b }
y = mkRecord { elemB b, elemA a }
-- type of `x` == type of `y`
当然,在上述案例get
和getA
中,只有适当的getB
函数才有效。
我也喜欢以下功能
slice :: Subset a b => a -> b
slice x = -- just remove the bits of x that aren't in type b.
add :: e -> a -> a ++ e
add e x = -- add an element to the "record" (compile error if it's already there)
我觉得这不是一个新问题所以也许已经存在解决方案。请注意,我并不要求解决方案是可扩展的,我需要处理的类型数量是有限且已知的,但当然可扩展且不会受到伤害。
我发现了一些似乎属于我所寻找的领域的软件包,即HList和extensible(可能是可扩展的,因为我想要更好我的记录无序)。我在Hackage文档中有点迷失,因此我只想要一些示例代码(或一些示例代码的链接),这些代码大致实现了我所寻找的目标。
答案 0 :(得分:1)
这正是HList
的好处。但是,由于我现在没有使用HList
包测试某些内容的正确设置(此外,它还有more confusing data definitions),这里是HList
的最小示例使用singletons
作为类型级列表的东西。
{-# LANGUAGE DataKinds, TypeOperators, GADTs,TypeFamilies, UndecidableInstances,
PolyKinds, FlexibleInstances, MultiParamTypeClasses
#-}
import Data.Singletons
import Data.Promotion.Prelude.List
data HList (l :: [*]) where
HNil :: HList '[]
HCons :: x -> HList xs -> HList (x ': xs)
add
函数最简单:它只是HCons
:
add :: x -> HList xs -> HList (x ': xs)
add = HCons
更有趣的是组合两个记录:
-- Notice we are using `:++` from singletons
combine :: HList xs -> HList ys -> HList (xs :++ ys)
combine HNil xs = xs
combine (x `HCons` xs) ys = x `HCons` (xs `combine` ys)
现在,对于get
函数,您需要根据类型级别列表进行调度。为此,您需要一个重叠类型类。
class Has x xs where
get :: xs -> x
instance {-# OVERLAPS #-} Has x (HList (x ': xs)) where
get (x `HCons` _) = x
instance Has x (HList xs) => Has x (HList (y ': xs)) where
get (_ `HCons` xs) = get xs
最后,我们可以使用Has
来定义类似的Subset
类。和以前一样的想法。
class Subset ys xs where
slice :: xs -> ys
instance Subset (HList '[]) (HList xs) where
slice _ = HNil
instance (Get y (HList xs), Subset (HList ys) (HList xs)) =>
Subset (HList (y ': ys)) (HList xs) where
slice xs = get xs `HCons` slice xs
正如您在parens中提到的那样,简单的HList
表单并不能确保您只有一个任何类型的字段(因此get
只返回第一个字段,忽略其余的部分)。如果您想要唯一性,只需向HList
构造函数添加约束即可。
data Record (l :: [*]) where
Nil :: Record '[]
Cons :: (NotElem x xs ~ 'True) => x -> Record xs -> Record (x ': xs)
但是,使用Subset
定义Record
似乎涉及一些证明。 :)