haskell - 如何在 ( :+:) f g p? (With :+: from GHC. Generics) 中为 p 指定类约束

标签 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’

最佳答案

如果您将 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 来获得它们。由于 MyClassmyFun 需要一个 SomeClass 实例,我们需要证明参数 Par1 永远不会出现在 Rep 中。 from' 将证明参数为空。

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

    default someFun :: (Generic a, MyClass (Rep a)) => a -> Maybe Int
    someFun a = myFun (from' a)

void 中的 Void 类型表示逻辑上不存在的类型。以下证明 Generic 的参数始终为空

-- Prove that the parameter is always empty
from' :: Generic a => a -> Rep a Void
from' = from

为了满足 myFunSomeClass 约束,我们为 Void 配备了一个 SomeClass 实例。我们可以确定 someFun::Void -> Maybe Int 永远不会被调用,因为没有 Void 类型的值传递给它。

instance SomeClass Void where
    someFun = absurd

现在我们可以为 SomeClass (Maybe Int) 派生一个实例,假设我们有一个 SomeClass Int 实例。

-- The following instances are needed for the composition of functors
instance SomeClass Int where
    someFun = Just

instance SomeClass a => SomeClass (Maybe a)

派生类

您不需要将 MyClassVoid 重用来派生 SomeClass 实例。相反,您可以为具有 myFun 的事物定义另一个类,而不管参数是什么。

class GSomeClass f where
    gsomeFun :: f a -> Maybe Int

您将为除 Par1Rec1 之外的所有内容编写 GSomeClass 实例,并使用 GSomeClass 派生 SomeClass 实例。 Generic 实例从不使用该参数,甚至对于像 Maybe a 这样的类型也不使用;相反,参数 a 的每次出现都显示为 K1 i a p

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

    default someFun :: (Generic a, GSomeClass (Rep a)) => a -> Maybe Int
    someFun a = gsomeFun (from a)

关于haskell - 如何在 ( :+:) f g p? (With :+: from GHC. Generics) 中为 p 指定类约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32711316/

相关文章:

haskell - 为什么 GHCi 在此错误消息中不显示多态类型?

haskell - 未找到堆栈初始化包

java - 有什么方法可以在 Java 中返回多态 this 吗?

java - 泛型和带有 Class.forName 的 asSubClass

c++ - 微软的edX C++类(class):double vs long double-为什么缺乏通用标准?

apache-spark - 从 Spark 到 Snowflake 数据类型

c++ - 通过别名定义前向定义的 C++ 结构

haskell - 在 Haskell 中使函数更短

haskell - 类型签名 […] 缺少随附的绑定(bind)

java - Mockito:使用泛型参数进行验证