使用类型族和泛型来查找 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 类型相关联的类型族(U1K1:*:, :+:).

我试过以下方法:

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 类型检查。 L1R1(:+:) 的构造函数,而不是 Crumbs。还有一个类型 GHC.Generics.R :: * 在类型级别隐藏了 CrumbsR 构造函数,但是您可以使用 '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