[英]Using type families and Generics to find an Id value
This question is related to this one , where I wanted to avoid the boilerplate of extracting an Id
value from a data structure, but in a type-safe manner. 这个问题关系到这一个 ,在这里我想避免提取的样板
Id
从数据结构值,但在类型安全的方式。
I'll repeat the relevant details of the problem here: suppose you have a type Id
: 我将在这里重复问题的相关细节:假设你有一个类型
Id
:
newtype Id = Id { _id :: Int }
And you want to define a function getId
that extracts this Id
from any structure that contains at least one Id
value: 并且您想要定义一个函数
getId
,它从包含至少一个Id
值的任何结构中提取此Id
:
class Identifiable e where
getId :: e -> Id
Now the problem is how to define such a class in a type-safe manner, while at the same time avoiding the boilerplate using Generics. 现在的问题是如何以类型安全的方式定义这样的类,同时避免使用泛型的样板 。
In my previous question I was pointed to type families, and in particular to the ideas described in this blog post . 在我之前的问题中,我被指出键入族,特别是本博客文章中描述的想法。 As far as I understand the idea is to define a type-class
MkIdentifiable
such that: 据我所知,这个想法是定义一个类型类
MkIdentifiable
这样:
class MakeIdentifiable (res :: Res) e where
mkGetId :: Proxy res -> e -> Id
Where a value is of type Res
only if there is at least one Id
value that is nested inside of it: 如果值的类型为
Res
仅当至少有一个嵌套在其中的Id
值时:
data Crumbs = Here | L Crumbs | R Crumbs
data Res = Found Crumbs | NotFound
Then, it seems, one could define: 然后,似乎可以定义:
instance MakeIdentifiable (Found e) e => Identifiable e where
getId = mkGetId (Proxy :: Proxy (Found e))
Now the question is how to define a type family for Res
that is associated to the types of GHC.Generics ( U1
, K1
, :*:
, :+:
). 现在的问题是如何为
Res
定义一个类型族,它与GHC.Generics( U1
, K1
, :*:
, :+:
:)的类型相关联。
I've tried the following: 我尝试过以下方法:
type family HasId e :: Res where
HasId Id = Found Here
HasId ((l :+: r) p) = Choose (HasId (l p)) (HasId (r p))
Where Choose
would be something like what is defined in the aforementioned blog post: Choose
将类似于上述博客文章中定义的内容:
type family Choose e f :: Res where
Choose (Found a) b = Found (L1 a)
Choose a (Found b) = Found (R1 b)
Choose a b = NotFound
But this won't compile as HasId (lp)
has kind Res
and a type is expected instead. 但是这不会编译,因为
HasId (lp)
具有类型Res
,而是预期类型。
You're pretty close to making Choose
typecheck. 你非常接近
Choose
。 L1
and R1
are constructors of (:+:)
, not Crumbs
. L1
和R1
是(:+:)
构造函数,而不是Crumbs
。 There's also a type GHC.Generics.R :: *
which hides the R
constructor from Crumbs
at the type level, but you can use 'R
to disambiguate (singly-quoted names are constructors, doubly-quoted are type constructors). 还有一个类型
GHC.Generics.R :: *
在类型级别隐藏来自Crumbs
的R
构造函数,但是你可以使用'R
来消除歧义(单引号名称是构造函数,双引号是类型构造函数)。
It's also good practice to annotate kinds of types, much like we annotate types of toplevel functions. 注释各种类型也是一种很好的做法,就像我们注释顶级函数的类型一样。
type family Choose (e :: Res) (f :: Res) :: Res where
Choose (Found a) b = Found ('L a)
Choose a (Found b) = Found ('R b)
Choose NotFound NotFound = NotFound -- I'm not a fan of overlapping families
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.