可扩展记录(我认为)

时间:2017-06-21 04:02:15

标签: haskell

我粗略想要的是:

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`

当然,在上述案例getgetA中,只有适当的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)

我觉得这不是一个新问题所以也许已经存在解决方案。请注意,我并不要求解决方案是可扩展的,我需要处理的类型数量是有限且已知的,但当然可扩展且不会受到伤害。

我发现了一些似乎属于我所寻找的领域的软件包,即HListextensible(可能是可扩展的,因为我想要更好我的记录无序)。我在Hackage文档中有点迷失,因此我只想要一些示例代码(或一些示例代码的链接),这些代码大致实现了我所寻找的目标。

1 个答案:

答案 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似乎涉及一些证明。 :)