可扩展记录(我认为) [英] Extensible records (I think)
问题描述
数据A = ...
数据B = ...
data C = ...
class HasA t其中
getA :: t - > A
类HasB t其中
getB :: t - > B
类HasC t其中
getC :: t - > C
所以我可以这样做(伪代码如下):
a :: A
b :: B
x = mkRecord {elemA a,elemB b}
y = mkRecord {elemB b,elemA a}
- `x`的类型==`y`的类型
当然,在上面的例子中,只有适当的 get
函数可以工作, getA
和 getB
。
我还想要以下功能:
slice ::子集ab => a - > b
slice x = - 只删除不在类型b中的x的位。
add :: e - > a - > a ++ e
add ex = - 向记录添加一个元素(如果它已经存在,则编译错误)
我觉得这不是一个新问题,所以也许已经存在一个解决方案。请注意,我并不要求解决方案是可扩展的,我需要处理的类型数量是有限且已知的,但当然并且可扩展的则不会受到伤害。
我发现了几个包,它们似乎处于我正在寻找的领域,即 HList 和可扩展(也许可扩展性更好,因为我想要我的记录无序的)。我在Hackage文档中有点失落,所以我只想要一些示例代码(或者一些示例代码的链接),它们大致可以实现我正在寻找的功能。
class有x xs其中
get :: xs - > ; x
实例{ - #OVERLAPS# - }有x(HList(x':xs))其中
get(x`HCons` _)= x
实例具有x(HList xs)=>有x(HList(y':xs))其中
获得(_`HCons` xs)=获得xs
最后,我们可以使用 Has
定义一个类似的子集
类。
class子集ys xs其中
slice :: xs - > ys
实例子集(HList'[])(HList xs)其中
slice _ = HNil
实例(Get y(HList xs),Subset ys)(HList xs))=>
子集(HList(y':ys))(HList xs)其中
slice xs = get xs`HCons` slice xs
正如你在parens中提到的那样,简单的 HList
表单并不能保证你只有一个的任何类型的字段(所以 get
只返回第一个字段,忽略其余字段)。如果你想要唯一性,你可以给 HList
构造函数添加一个约束。
Nil :: Record'[]
Cons ::(NotElem x xs〜'True)=> x - >记录xs - > Record(x':xs)
然而,定义子集
使用记录
看起来像涉及一些证明。 :)
What I roughly want is this:
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
So I can do something like this (pseudocode follows):
a :: A
b :: B
x = mkRecord { elemA a, elemB b }
y = mkRecord { elemB b, elemA a }
-- type of `x` == type of `y`
Naturally, only the appropriate get
functions work, in the above case getA
and getB
.
I'd also like the following functions
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)
I feel like this is not a new problem so perhaps a resolution for this already exists. Note that I don't require the solution to be extensible, the amount of types I need to deal with is finite and known, but of course and extensible one wouldn't hurt.
I've found a couple of packages that seem to be in the field of what I'm looking for, namely HList and extensible (perhaps extensible is better because I want my records unordered). I got a bit lost in the Hackage docs so I'd like just some sample code (or a link to some sample code) that roughly achieves what I'm looking for.
This is exactly what HList
is good for. However, since I don't have the right setup to test something with the HList
package right now (and besides, it has more confusing data definitions), here is a minimal example of HList
that uses singletons
for the type-level list stuff.
{-# 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)
The add
function is the simplest: it is just HCons
:
add :: x -> HList xs -> HList (x ': xs)
add = HCons
Something more interesting is combining two records:
-- 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)
Now, for your get
function, you need to dispatch based on the type-level list. To do this, you need an overlapping type class.
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
Finally, we can use Has
to define a similar Subset
class. Same idea as before.
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
As you mention in parens, the simple HList
form does not ensure you have only one of any type of field (so get
just returns the first field, ignoring the rest). If you want uniqueness, you can just add a constraint to the HList
constructor.
data Record (l :: [*]) where
Nil :: Record '[]
Cons :: (NotElem x xs ~ 'True) => x -> Record xs -> Record (x ': xs)
However, defining Subset
using Record
looks like it involves some proofs. :)
这篇关于可扩展记录(我认为)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!