如何为p in(:+ :) f g p指定类约束? (附:+:来自GHC.Generics)

时间:2015-09-22 07:52:03

标签: haskell generics types

在构造函数之间编码选择GHC.Generics定义以下类型:

data    (:+:) f g p = L1 (f p) | R1 (g p)

Generic类提供了将Generic类型转换为表示的方法:

from :: a -> Rep a x

要编写一个类型泛型的函数,我定义了一个处理表示的类:

class MyClass r where myFun :: r a -> Maybe Int

假设我还有一个我为其定义实例的类SomeClass:

instance (SomeClass (f p),SomeClass (g p)) => SomeClass ((:+:) f g p) where
  someFun (R1 _) = Just 42

如何将SomeClass约束添加到Generic sum类型的MyClass实例?换句话说,以下实例有什么问题:

instance (SomeClass (f p), SomeClass (g p), MyClass f, MyClass g)
      => MyClass ((:+:) f g) where
 myFun (L1 x) = myFun x
 myFun y      = someFun y -- Error: Could not deduce (SomeClass (f a))
                          -- arising from a use of ‘someFun’

我写的一个完整的例子是:

{-# LANGUAGE TypeOperators, DefaultSignatures, DeriveGeneric, FlexibleContexts, 
             UndecidableInstances, AllowAmbiguousTypes, RankNTypes #-}
module M where
import GHC.Generics

---

class SomeClass a where 
  someFun :: a -> Maybe Int
  default someFun :: (Generic a, MyClass (Rep a)) => a -> Maybe Int
  someFun x = myFun (from x)

instance (SomeClass (f p),SomeClass (g p)) => SomeClass ((:+:) f g p) where
  someFun (R1 _) = Just 42

instance SomeClass Int where
  someFun i  = Just i

---

class MyClass r where 
  myFun :: r a -> Maybe Int

instance (SomeClass a) => MyClass (K1 i a) where
  myFun (K1 x) = someFun x -- This is fine

instance (SomeClass (f p), SomeClass (g p), MyClass f, MyClass g) => MyClass ((:+:) f g) where
  myFun (L1 x) = myFun x
  myFun y      = someFun y -- Error: Could not deduce (SomeClass (f a)) arising from a use of ‘someFun’

2 个答案:

答案 0 :(得分:3)

如果您向SomeClass a添加myFun约束,则无其他事情可做。

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}

import Control.Applicative
import GHC.Generics

class SomeClass a where
    someFun :: a -> Maybe Int

class MyClass f where
    myFun :: SomeClass a => f a -> Maybe Int

    default myFun :: (Generic1 f, MyClass (Rep1 f), SomeClass a) => f a -> Maybe Int
    myFun f = myFun (from1 f)

您可以为通用表示中使用的所有数据类型编写实例。其中最有趣的是Par1,它实际上使用SomeClass a约束在参数出现时使用someFun

-- occurences of the parameter
instance MyClass Par1 where
    myFun (Par1 p) = someFun p

-- recursions of kind *
instance SomeClass a => MyClass (K1 i a) where
    myFun (K1 a) = someFun a

-- recursions of kind * -> *
instance MyClass f => MyClass (Rec1 f) where
    myFun (Rec1 f) = myFun f

-- constructors with no arguments
instance MyClass U1 where
    myFun (U1) = Nothing -- or Just 0 or Just 1 depending on what you're doing

-- constructors with multiple arguments
instance (MyClass f, MyClass g) => MyClass (f :*: g) where
    myFun (f :*: g) = liftA2 (+) (myFun f) (myFun g)  -- or howerever you are going to combine the Maybe Int

-- data types with multiple constructors
instance (MyClass f, MyClass g) => MyClass (f :+: g) where
    myFun (L1 f) = myFun f
    myFun (R1 g) = myFun g

-- metadata
instance (MyClass f) => MyClass (M1 i c f) where
    myFun (M1 f) = myFun f

如果你想支持仿函数的组合,我们必须更聪明一些。明显的定义需要SomeClass (Maybe Int)实例。

-- composition of functors
instance (MyClass f, MyClass g, Functor f) => MyClass (f :.: g) where
    myFun (Comp1 fg) = myFun $ fmap myFun fg

从MyClass

派生SomeClass

我们会让SomeClass个实例一般重用MyClass来获取它们。由于MyClass的{​​{1}}需要myFun个实例,我们需要证明参数SomeClass永远不会出现在Par1中。 Rep将证明该参数为空。

from'

void中的class SomeClass a where someFun :: a -> Maybe Int default someFun :: (Generic a, MyClass (Rep a)) => a -> Maybe Int someFun a = myFun (from' a) 类型表示无法逻辑存在的类型。以下证明Void的参数始终为空

Generic

为了满足-- Prove that the parameter is always empty from' :: Generic a => a -> Rep a Void from' = from 的{​​{1}}约束,我们为SomeClass实例配备myFun个实例。我们可以肯定Void永远不会被调用,因为没有类型SomeClass的值传递给它。

someFun :: Void -> Maybe Int

现在我们可以为Void派生一个实例,假设我们有instance SomeClass Void where someFun = absurd 个实例。

SomeClass (Maybe Int)

派生SomeClass

您无需重复使用SomeClass Int -- The following instances are needed for the composition of functors instance SomeClass Int where someFun = Just instance SomeClass a => SomeClass (Maybe a) 来派生MyClass个实例。相反,无论参数是什么,您都可以为具有Void的事物定义另一个类。

SomeClass

您为myFunclass GSomeClass f where gsomeFun :: f a -> Maybe Int 以外的所有内容编写GSomeClass个实例,并使用Par1派生Rec1个实例。 GSomeClass个实例从不使用参数,即使对于SomeClass等类型也不行;相反,参数Generic的每次出现都显示为Maybe a

a

答案 1 :(得分:0)

Haskell目前无法编写类型forall p. SomeClass (f p) =>的约束,这是您在类型中尝试做的事情

(SomeClass (f p), SomeClass (g p), MyClass f, MyClass g) => MyClass ((:+:) f g)

使用第二个类型类捕获这些约束是一个技巧。您可以编写代表FSomeClass f的第二个班级forall p. SomeClass (f p)。如果SomeClass很简单,您只需在SomeClass的字典中为FSomeClass重新生成字典的字段,并使用f a代替a

class FSomeClass f where
    fsomeFun :: f a -> Maybe Int

如果SomeClass更复杂,或者您需要为依赖于它的代码提供真实的SomeClass实例,则可以在{{{{{}}中捕获整个SomeClass字典。 1}}。 GADT捕获任何约束的字典。

Dict

使用{-# LANGUAGE GADTs #-} {-# LANGUAGE ConstraintKinds #-} data Dict c where Dict :: c => Dict c 我们可以编写一个类,对于每个Dicta都有一个f a实例被困在SomeClass。< / p>

Dict

要进入class FSomeClass f where someDict :: p0 a -> p1 f -> Dict (SomeClass (f a)) -- | | ^ there's a SomeClass (f a) instance -- | ^ for this f -- ^ for any a 实例,您需要在字典上进行模式匹配。

SomeClass和FSomeClass实例

我们将为来自SomeClass (f a)SomeClass (f p)个实例的所有类型制作GHC.Generics个实例,以证明FSomeClass f可能因所有类型而异。

p

对于{-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} ,我们无法对参数Par1执行任何操作,因为它可能会因所有类型而异。我们唯一的另一种选择是不提供p实例。对于SomeClass (Par1 p)Par1K1U1个实例没有约束,SomeClass实例只捕获FSomeClass字典。

SomeClass

instance SomeClass (Par1 p) where someFun (Par1 p) = Nothing -- You can't do anything with the p; you know nothing about it. instance FSomeClass Par1 where someDict _ _ = Dict instance SomeClass a => SomeClass (K1 i a p) where someFun (K1 a) = someFun a instance SomeClass a => FSomeClass (K1 i a) where someDict _ _ = Dict instance SomeClass (U1 p) where someFun (U1) = Nothing -- or Just 0 or Just 1 depending on what you're doing instance FSomeClass U1 where someDict _ _ = Dict SomeClass的{​​{1}}个实例需要M1约束。在我们构建Rec1之前,例如SomeClass (f p) =>我们必须为Dict引入字典。我们从SomeClass (Rec1 f p) SomeClass (f p)实例获取SomeClass (f p)字典,并在其FSomeClass上获取模式匹配。

f

对于产品和总和,Dict实例有两个约束,因此我们得到两个字典的模式匹配。我们以同样的方式处理仿函数的组合,但我为了简洁而跳过它。

instance SomeClass (f p) => SomeClass (Rec1 f p) where
    someFun (Rec1 f) = someFun f

instance (FSomeClass f) => FSomeClass (Rec1 f) where
    someDict pa _ = case someDict pa (Proxy :: Proxy f) of Dict -> Dict

instance SomeClass (f p) => SomeClass (M1 i c f p) where
    someFun (M1 f) = someFun f

instance (FSomeClass f) => FSomeClass (M1 i c f) where
    someDict pa _ = case someDict pa (Proxy :: Proxy f) of Dict -> Dict

使用FSomeClass

使用原始SomeClass

instance (SomeClass (f p), SomeClass (g p)) => SomeClass ((f :*: g) p) where
    someFun (f :*: g) = liftA2 (+) (someFun f) (someFun g)   -- or howerever you are going to combine the Maybe Int

instance (FSomeClass f, FSomeClass g) => FSomeClass (f :*: g) where
    someDict pa _ = case someDict pa (Proxy :: Proxy f) of
                       Dict -> case someDict pa (Proxy :: Proxy g) of
                           Dict -> Dict

instance (SomeClass (f p), SomeClass (g p)) => SomeClass ((f :+: g) p)  where
    someFun (L1 f) = someFun f
    someFun (R1 g) = someFun g

instance (FSomeClass f, FSomeClass g) => FSomeClass (f :+: g) where
    someDict pa _ = case someDict pa (Proxy :: Proxy f) of
                       Dict -> case someDict pa (Proxy :: Proxy g) of
                           Dict -> Dict

我们将为MyClass编写一个实例。我们将采用您的原始签名,并将class MyClass f where myFun :: f a -> Maybe Int 替换为:+:

SomeClass (f p)

FSomeClass f的分支字面上使用instance (SomeClass (f p), SomeClass (g p), MyClass f, MyClass g) => MyClass ((:+:) f g) instance (FSomeClass f, FSomeClass g , MyClass f, MyClass g) => MyClass ((:+:) f g)

R1

我不建议为someFun编写非关联的实例。如果要以不同方式处理第一个构造函数,则应保证以后的构造函数不会以与第一个构造函数相同的方式处理。数据类型

instance (FSomeClass f, FSomeClass g, MyClass f, MyClass g) => MyClass (f :+: g) where
    myFun (L1 f) = myFun f
    myFun (R1 g) = case someDict g (Proxy :: Proxy g) of
                       Dict -> someFun g

有多个可能的代表。采用元数据自由,:+:的两种可能表示是

data MySum = A | B | C

构造函数的表示可以是

MySum