为Haskell中的类型派生自定义类的实例

为Haskell中的类型派生自定义类的实例,haskell,generic-programming,Haskell,Generic Programming,我正试图减少副驾驶的样板文件数量 在最新版本中,我们添加了结构,使用它们需要声明两个实例。下面是一个例子 对于数据类型: data TestStruct = TestStruct { i :: Field "i" Int8 } 我们需要以下实例: instance Struct TestStruct where typename _ = "teststruct" toValues t = [ Value Int8 (i t) ] instance Typed TestStru

我正试图减少副驾驶的样板文件数量

在最新版本中,我们添加了结构,使用它们需要声明两个实例。下面是一个例子

对于数据类型:

data TestStruct = TestStruct
  { i :: Field "i" Int8
  }
我们需要以下实例:

instance Struct TestStruct where
  typename _ = "teststruct"
  toValues t = [ Value Int8 (i t) ]

instance Typed TestStruct where
  typeOf = Struct (TestStruct { i = Field 0 })
我希望通过自动生成实例使其更易于使用

我一直在研究泛型,以前也使用过模板Haskell,但在过去几年中情况发生了很大变化。许多软件包和推荐的解决方案现在都过时了,我花了几个月的时间浏览过去10年的论文和图书馆,后来才发现它们不再使用了。我很难理解编译器已经可以自动完成什么,我应该使用什么泛型扩展或包(如果有的话),并且通常很难找到一种最新的方法,这种方法可以工作并将继续工作


自动为这些类生成实例的最佳方法是什么?(如果可能的话,我想避免使用TH,但我知道从今天起可能不可能实现。)

在大多数情况下,只需咨询

@Jon Purdy在评论中描述的方法是有价值的,我的回答基于此

派生
类型化
足够简单-已经存在为基类型和泛型类型生成默认值的实现。看起来很合适,但这不是一个建议:我只是选择了第一个Hoogle结果之一

defaulttypeof::(可键入a、结构a、默认a)=>键入a
typeOf=Struct def
然后就可以派生出
默认值
类型
,以获得良好的实现

上述要求为
字段
提供一个
默认
实例,但这几乎是显而易见的

实例(默认值f)=>默认值(字段nf),其中
def=字段def
派生
Struct
这是一个更棘手的问题,因为它需要触及泛型的本质。然而,看看Copilot的具体用例,我们似乎可以忽略很多可能的事情(比如多个构造函数等等)

这个答案的较长形式还可以说,如果有必要,可以为
D1
C1
等对象提供递归实例

派生
typename
不需要-只需使用。如果您确实想要,请定义一个类

class TypeName'f其中
typename'::f p->String
但是对于
D1
,它只需要一个实例

派生
toValues
这将需要更多的时间。定义一个类

class ToValues'a f其中
toValues':代理a->f p->[值a]
a
是一个类参数,因为它在机械上必须是类参数,
Proxy
是一个函数参数,因为否则GHC会抱怨
a
没有出现,并且没有资金来帮助解决该问题

通过组合多个字段的产品表示法来表示多个字段。这意味着单例字段
S1
需要有一个实例,两个子实例的乘积需要有一个实例

定义实例并不难

实例(键入c,知道符号字段名)=>ToValues'a(s1m(Rec0(字段名c)),其中
toValues'p(M1(K1字段))=[值(typeOf@c)字段]
上面的实例为每个字段提供了一个单例,产品可以执行串联

instance(ToValues'al,ToValues'ar)=>ToValues'a(l:::r)其中
toValues'p(l::::r)=toValues'pl++toValues'pr
实例 我在这里使用类型统一
~
进行类型级模式匹配

defaulttypename::(泛型a,repa~D1('MetaData typename m p nt)f),KnownSymbol typename=>a->String
typename=toLower(symbolVal(Proxy@typename))
默认toValues::(通用a,代表a~D1 m1(C1 m2 f),toValues'a f=>a->[值a]
toValues a=toValues'(代理@a)字段
其中(M1(M1字段))=(从a开始)
toLower
只是一个
String
-小写函数,尽管您必须定义它
symbolVal
给出类型级别
Symbol
字符串

toValues
代码通过将
repa
约束为
D1(C1)
的形式,假设存在单个数据构造函数


和以前一样,现在可以只导出
泛型
结构
来获得这个有用的实例。

我没有时间详细说明这个问题,但是我在这里通常使用的技术是使用
Generic
中的
default
方法编写
Struct
Typed
,这允许使用
DeriveGeneric
+
DeriveAnyClass
编写
派生(Generic,Struct,Typed)
(或
派生股票(Generic)
+
使用显式
派生策略派生任何类(结构,类型)