我如何编写一个函数 `运行` 类 `运行State` 或 `运行ReaderT`?

How can I write a function `run` that calls `runStateT` or `runReaderT`?

我如何编写一个通用函数 run 接受一些 monad 转换器的对象,并调用相应的函数?

鉴于 run s

我试过创建类型类 Runnable:

:set -XMultiParamTypeClasses
:set -XFlexibleInstances

class Runnable a b where
  run :: a -> b
  (//) :: a -> b
  (//) = run

instance Runnable (StateT s m a) (s -> m (a, s)) where
 run = runStateT

instance Runnable (ReaderT r m a) (r -> m a) where
 run = runReaderT

但是当我尝试使用 run 时,它不起作用。例如,让我们定义 simpleReader,它只是 returns 10 当读取:

simpleReader = ReaderT $ \env -> Just 10

runReaderT simpleReader ()

这会像预期的那样输出 Just 10

但是,当我尝试使用 run 时,出现错误:

run simpleReader ()
<interactive>:1:1: error:
    • Non type-variable argument in the constraint: Runnable (ReaderT r Maybe a) (() -> t)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

如果我像它建议的那样启用 FlexibleContexts,我会得到一个不同的错误:

<interactive>:1:1: error:
    • Could not deduce (Runnable (ReaderT r0 Maybe a0) (() -> t))
        (maybe you haven't applied a function to enough arguments?)
      from the context: (Runnable (ReaderT r Maybe a) (() -> t), Num a)
        bound by the inferred type for ‘it’:
                   forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t
        at <interactive>:1:1-19
      The type variables ‘r0’, ‘a0’ are ambiguous
    • In the ambiguity check for the inferred type for ‘it’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

run的输出类型完全由它的输入类型决定。将此表示为函数依赖项 (-XFunctionalDependencies):

class Runnable a b | a -> b where
  run :: a -> b
  -- side note: (//) does not belong here
(//) :: Runnable a b => a -> b
(//) = run
-- instances as given

现在可以了。您的版本不起作用的原因是因为无法知道输出类型应该是什么。如果你有

act :: ReaderT Env Identity Ret

然后

run act :: Runnable (ReaderT Env Identity Ret) b => b

然后我们就卡住了,没有办法弄清楚 b 应该是什么。这是例如可以添加另一个实例

instance Runnable (ReaderT r m a) (ReaderT r m a) where
  run = id

现在 run act 可以是 ReaderT Env Identity RetEnv -> Identity Ret。依赖项 a -> b a) 允许我们从 a 推断出 b 和 b) 限制 instance 声明使之成为可能。像我给出的那样的冲突实例被拒绝,并且 run act 通过查看你给出的 instanceb 类型推断为 Env -> Identity Ret,如你所愿。

简短回答: 您需要 class.

的功能依赖

长答案:

当编译器看到 run 时,它需要找到 Runnable 的适当实例,以确定要使用 run 的哪个实现。为了找到那个实例,它需要知道 ab 是什么。它知道a是一个ReaderT,所以一个就被覆盖了。但是 b 是什么?

编译器发现您正在使用 b 作为函数,并将 () 作为参数传递给它。因此,编译器认为 b 必须是 () -> t 的形状,其中 t 还未知。

这就是它有点停止的地方:编译器无处可以从中获取 t,因此它不知道 b,因此找不到合适的实例,所以 kaboom !

但是有一种方法可以解决这种情况。如果我们仔细看看你的 Runnable class 的实际含义,很容易看出 b 应该严格由 a 定义。也就是说,如果我们知道 monad 是什么,我们就知道 return 值是什么。因此,编译器应该可以通过知道a来确定b。但是,唉,编译器不知道!

但是有一种方法可以向编译器解释这一点。叫做"functional dependency",写法是这样的:

class Runnable a b | a -> b where

这个符号 a -> b 告诉编译器 b 应该由 a 明确确定。这将意味着,一方面,编译器不会让您定义违反此规则的实例,另一方面,它将能够通过知道 [=14] 来找到 Runnable 的适当实例=],然后根据该实例确定 b