简体   繁体   English

使用类型族和泛型来查找Id值

[英]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( U1K1:*::+: :)的类型相关联。

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 . L1R1(:+:)构造函数,而不是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 :: *在类型级别隐藏来自CrumbsR构造函数,但是你可以使用'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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM