使用类型族和泛型来查找 Id 值
Using type families and Generics to find an Id value
这个问题与 this one 有关,我想避免从数据结构中提取 Id
值的样板,但要以类型安全的方式进行。
我会在这里重复问题的相关细节:假设你有一个类型 Id
:
newtype Id = Id { _id :: Int }
并且您想定义一个函数 getId
从任何包含至少一个 Id
值的结构中提取此 Id
:
class Identifiable e where
getId :: e -> Id
现在的问题是如何以类型安全的方式定义这样的class,同时避免样板 使用泛型。
在我的 previous question I was pointed to type families, and in particular to the ideas described in this blog post。据我了解,这个想法是定义一个类型-class MkIdentifiable
这样:
class MakeIdentifiable (res :: Res) e where
mkGetId :: Proxy res -> e -> Id
只有当至少有一个 Id
值嵌套在其中时,一个值才属于 Res
类型:
data Crumbs = Here | L Crumbs | R Crumbs
data Res = Found Crumbs | NotFound
那么,似乎可以定义:
instance MakeIdentifiable (Found e) e => Identifiable e where
getId = mkGetId (Proxy :: Proxy (Found e))
现在的问题是如何为 Res
定义与 GHC.Generics 类型相关联的类型族(U1
、K1
、:*:
, :+:
).
我试过以下方法:
type family HasId e :: Res where
HasId Id = Found Here
HasId ((l :+: r) p) = Choose (HasId (l p)) (HasId (r p))
其中 Choose
类似于上述博客中定义的内容 post:
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
但这不会编译,因为 HasId (l p)
有种类 Res
并且需要一个类型。
您即将完成 Choose
类型检查。 L1
和 R1
是 (:+:)
的构造函数,而不是 Crumbs
。还有一个类型 GHC.Generics.R :: *
在类型级别隐藏了 Crumbs
的 R
构造函数,但是您可以使用 'R
来消除歧义(单引号是构造函数,双引号是引用的是类型构造函数)。
注释类型也是一种很好的做法,就像我们注释顶层函数的类型一样。
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
这个问题与 this one 有关,我想避免从数据结构中提取 Id
值的样板,但要以类型安全的方式进行。
我会在这里重复问题的相关细节:假设你有一个类型 Id
:
newtype Id = Id { _id :: Int }
并且您想定义一个函数 getId
从任何包含至少一个 Id
值的结构中提取此 Id
:
class Identifiable e where
getId :: e -> Id
现在的问题是如何以类型安全的方式定义这样的class,同时避免样板 使用泛型。
在我的 previous question I was pointed to type families, and in particular to the ideas described in this blog post。据我了解,这个想法是定义一个类型-class MkIdentifiable
这样:
class MakeIdentifiable (res :: Res) e where
mkGetId :: Proxy res -> e -> Id
只有当至少有一个 Id
值嵌套在其中时,一个值才属于 Res
类型:
data Crumbs = Here | L Crumbs | R Crumbs
data Res = Found Crumbs | NotFound
那么,似乎可以定义:
instance MakeIdentifiable (Found e) e => Identifiable e where
getId = mkGetId (Proxy :: Proxy (Found e))
现在的问题是如何为 Res
定义与 GHC.Generics 类型相关联的类型族(U1
、K1
、:*:
, :+:
).
我试过以下方法:
type family HasId e :: Res where
HasId Id = Found Here
HasId ((l :+: r) p) = Choose (HasId (l p)) (HasId (r p))
其中 Choose
类似于上述博客中定义的内容 post:
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
但这不会编译,因为 HasId (l p)
有种类 Res
并且需要一个类型。
您即将完成 Choose
类型检查。 L1
和 R1
是 (:+:)
的构造函数,而不是 Crumbs
。还有一个类型 GHC.Generics.R :: *
在类型级别隐藏了 Crumbs
的 R
构造函数,但是您可以使用 'R
来消除歧义(单引号是构造函数,双引号是引用的是类型构造函数)。
注释类型也是一种很好的做法,就像我们注释顶层函数的类型一样。
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