没有因使用“from”而产生的 (Generic (fa)) 实例

sgi*_*lis 3 haskell generic-programming ghc-generics

我在为以下代码找到合适的类型约束时遇到问题

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
import GHC.Generics

data Value = One | Two deriving Generic

class Class a where
    cname :: a -> String -> Bool
    default cname :: (Generic a, GClass (Rep a))
               => a -> String -> Bool
    cname = gname . from

class GClass f where
    gname :: f a -> String -> Bool

instance GClass (f :+: g) where
    gname (L1 x) s | conName (from x) == s = True
                   | otherwise             = False
    gname (R1 x) s | conName (from x) == s = True
                   | otherwise             = False
Run Code Online (Sandbox Code Playgroud)

它失败了

No instance for (Generic (f a)) arising from a use of `from'
Run Code Online (Sandbox Code Playgroud)

gname像这样添加约束

instance (Generic (f a)) => GClass (f :+: g) where
Run Code Online (Sandbox Code Playgroud)

失败

Could not deduce (Generic (f a1)) arising from a use of `from'
from the context (Generic (f a))
Run Code Online (Sandbox Code Playgroud)

编辑:完整片段的完整错误消息

Generic.hs:19:31:
    No instance for (Generic (f a)) arising from a use of `from'
    Possible fix: add an instance declaration for (Generic (f a))
    In the first argument of `conName', namely `(from x)'
    In the first argument of `(==)', namely `conName (from x)'
    In the expression: conName (from x) == s

Generic.hs:21:31:
    No instance for (Generic (g a)) arising from a use of `from'
    Possible fix: add an instance declaration for (Generic (g a))
    In the first argument of `conName', namely `(from x)'
    In the first argument of `(==)', namely `conName (from x)'
    In the expression: conName (from x) == s
Run Code Online (Sandbox Code Playgroud)

这是 GHC 7.6.3

Cir*_*dec 5

I presume you are trying to get the constructor names using Ghc.Generics. Constructor, field, and data type metadata is held in M1 nodes. M1 nodes are tagged with either D, C, or S to indicate whether they hold datatype, constructor, or selector (field) metadata.

I've simplified your Class and GClass to return the outermost constructor name instead of checking to see if it is a certain name. I'm interpreting Class as the class of types whose values have an outermost constructor with a name.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
import GHC.Generics

data Value = One | Two deriving Generic

class Class a where
    cname :: a -> String
    default cname :: (Generic a, GClass (Rep a))
               => a -> String
    cname = gname . from

class GClass f where
    gname :: f a -> String
Run Code Online (Sandbox Code Playgroud)

We'd like to be able to derive a Class instance for Value and observe that cname One == "One" and cname Two == "Two".

instance Class Value    

main = do
    print . cname $ One
    print . cname $ Two
Run Code Online (Sandbox Code Playgroud)

We need to implement GClass for three of the representation nodes to be able to do this. The representation for One is:

> from One
M1 {unM1 = L1 (M1 {unM1 = U1})}
Run Code Online (Sandbox Code Playgroud)

The outer M1 is an M1 D holding in a dictionary the metadata for the Value datatype. The L1 is selecting the first constructor, One. The inner M1 is an M1 C holding in a dictionary the metadata for the One constructor. We don't care about anything deeper than it, since that M1 represents the outermost constructor.

The most interesting node is the inner M1 C which holds the constructor metadata. We can get the constructor name as long as the metadata implements the Constructor class. The Constructor class includes conName which returns the constructor name given an appropriate proxy, and the appropriate proxy type is designed to looks like the type of M1 C.

conName :: Constructor c => t    c (f :: * -> *) a -> [Char]
                            M1 C c  f            p
Run Code Online (Sandbox Code Playgroud)

这意味着我们可以GClass简单地为M1 C节点实现,只要有一个Constructor元数据标签的实例c

instance (Constructor c) => GClass (M1 C c f) where
    gname = conName
Run Code Online (Sandbox Code Playgroud)

当我们面临两个构造函数之间的选择时,:+:如果我们能够确定两个构造函数的最外层构造函数名称,我们就可以确定最外层的构造函数名称。

instance (GClass f, GClass g) => GClass (f :+: g) where
    gname (L1 x) = gname x
    gname (R1 x) = gname x
Run Code Online (Sandbox Code Playgroud)

当我们处理数据类型的元数据节点M1 D时,当我们可以确定没有元数据节点的表示的最外层构造函数名称时,我们可以确定最外层构造函数名称。

instance GClass f => GClass (M1 D c f) where
    gname (M1 x) = gname x
Run Code Online (Sandbox Code Playgroud)

通过这三个实例,我们可以运行我们想要的代码并看到

> cname One
"One"
> cname Two
"Two"
Run Code Online (Sandbox Code Playgroud)