参数类型类

Parametric typeclasses

有没有一种方法可以根据类型参数 d 声明 Conf 的泛型参数化类型 offers/provides 函数 frames,例如

{-# LANGUAGE GeneralizedNewtypeDeriving
           , MultiParamTypeClasses
           , FunctionalDependencies #-}

import Control.Applicative
import Control.Monad
import Control.Monad.Identity
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Control.Monad.Trans.Except

data MyConf d = MyConf { frames :: [d] } -- my parametric type

-- define a class to expose the interface in monads
class ConfM d m | m -> d where
    getFrames :: m [d]

-- wrap StateT to include MyConf and allow instance of ConfM
newtype MyStateT d m a = MyStateT { runMyStateT :: StateT (MyConf d) m a }
    deriving (Functor, Applicative, Monad, MonadTrans)

-- expose the interface over selected transformers
instance Monad m => ConfM d (MyStateT d m) where
    getFrames = MyStateT $ fmap frames get

instance (ConfM d m, Monad m) => ConfM d (ExceptT a m) where
    getFrames = lift getFrames

这样就可以这样写:

-- hide the gory implementation details
type MyMonad d = ExceptT A (MyStateT d B) C

class SomeClass a

-- this is the desired goal:
-- to concisely write an 'algorithm' in MyMonad only once
-- but for all possible choices of d from SomeClass
example :: SomeClass d => MyMonad d
example = do
    fs <- getFrames
    -- do SomeClass stuff with d
    return ()

-- assume Int is instance of SomeClass
instance SomeClass Int

-- give me an instance of the above generic 'algorithm'
exampleInt :: MyMonad Int
exampleInt = example

-- assuming for example
type A = ()
type B = Identity
type C = ()

上面的代码我卡在了:

test.hs:23:25:
    Illegal instance declaration for ‘ConfM d (MyStateT d m)’
      (All instance types must be of the form (T a1 ... an)
       where a1 ... an are *distinct type variables*,
       and each type variable appears at most once in the instance head.
       Use FlexibleInstances if you want to disable this.)
    In the instance declaration for ‘ConfM d (MyStateT d m)’

test.hs:26:38:
    Illegal instance declaration for ‘ConfM d (ExceptT a m)’
      (All instance types must be of the form (T a1 ... an)
       where a1 ... an are *distinct type variables*,
       and each type variable appears at most once in the instance head.
       Use FlexibleInstances if you want to disable this.)
    In the instance declaration for ‘ConfM d (ExceptT a m)’

还有额外的FlexibleInstances

test.hs:27:14:
    Illegal instance declaration for ‘ConfM d (ExceptT 
      The coverage condition fails in class ‘ConfM’
        for functional dependency: ‘m -> d’
      Reason: lhs type ‘ExceptT a m’ does not determine
      Using UndecidableInstances might help
    In the instance declaration for ‘ConfM d (ExceptT a

所以我需要UndecidableInstances.

您的问题似乎有点含糊,但听起来像是具有函数依赖性的多参数类型class的潜在用例。

{-# LANGUAGE  MultiParamTypeClasses
            , FunctionalDependencies #-}

class Monad m => MyClass d m | m -> d where
  getDs :: m [d]

那么MyClass d m意味着m是一个Monad并且getDs可以用来产生m [d]类型的值。函数依赖的目的是表明 m 决定 d。对于每个 m,只有一个 MyClass 的实例声明,并且 class 必须确定 d 是什么。所以你可以写一个像

这样的实例
instance MyClass Int IO where ...

(这将是允许IO),但不是像

这样的软弱无力的东西
instance MyClass d IO where ...

因为后者没有确定d.


您可能会觉得我为 MyClass 选择的参数顺序有点奇怪。这种疯狂有一些方法。主要原因是MyClass可以部分应用。将它部分应用于 m 并不是很有用,因为这会留下一个只能由一个可能的参数满足的约束。另一方面,将其部分应用于 d 可能会有用,因为对于 d 的给定选择,可能有 m 的多个选择。因此给出 {-# LANGUAGE ConstraintKinds #-},

type MakesInts = MyClass Int

是一个可能有用的约束。我相信在某些情况下使用此顺序 可能 也有助于避免需要 UndecidableInstances,但我不确定。


其他人提到的替代方法是使用关联类型族。

{-# LANGUAGE TypeFamilies #-}

class Monad m => MyClass m where
  type Available m
  getDs :: m [Available m]

这基本上做同样的事情,但是

  1. 任何编写 MyClass 实例的人都必须包含一行,例如 type Available IO = Int.

  2. 任何对 Available 类型施加约束的人都需要在约束中使用 Available,并且需要 FlexibleContexts(没什么大不了的)。

  3. 类型系列提供对关联类型的访问。

  4. 类型族在 GHC 核心(又名系统 FC)中表达,因此在某些方面它们比函数依赖表现得更好。

1(特别是)和 2 可以说是类型族方法的缺点; 3和4是优势。这在很大程度上归结为品味问题。