【问题标题】:How can I write a function `run` that calls `runStateT` or `runReaderT`?如何编写一个调用 `runStateT` 或 `runReaderT` 的函数 `run`?
【发布时间】:2019-10-29 19:09:02
【问题描述】:

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

给定run s

  • 如果sStateT,则run = runStateT
  • 如果sReaderTrun = runReaderT
  • 如果sMaybeT,则run = runMaybeT

我已经尝试创建一个类型类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,它在读取时只返回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

【问题讨论】:

  • 怎么不工作?
  • 我添加了一些信息来解释我遇到了什么错误

标签: haskell monads typeclass monad-transformers


【解决方案1】:

简答:你需要一个函数依赖于你的类。

长答案

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

编译器看到您使用b 作为函数,将() 作为参数传递给它。因此,认为编译器,b 必须是 () -&gt; t 的形状,而 t 尚不清楚。

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

但是对于这种情况有补救措施。如果我们仔细看看你的Runnable 类的实际含义,很容易看出b 应该由a 严格定义。也就是说,如果我们知道 monad 是什么,我们就知道返回值是什么。因此,编译器应该可以通过知道a 来确定@​​987654339@。但是很可惜,编译器不知道这一点!

但是有一种方法可以向编译器解释这一点。叫做“函数依赖”,写成这样:

class Runnable a b | a -> b where

这个符号a -&gt; b 告诉编译器b 应该由a 明确确定。这意味着,一方面,编译器不会让你定义违反此规则的实例,另一方面,它可以通过知道a 找到合适的Runnable 实例,并且然后从该实例中确定b

【讨论】:

    【解决方案2】:

    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 -&gt; Identity Ret。依赖a -&gt; b a) 允许我们从a 推断b 和b) 限制instance 声明以使这成为可能。像我给出的那样有冲突的实例被拒绝,run actb 类型通过查看您给出的Env -&gt; Identity Ret 推断为Env -&gt; Identity Ret,根据需要。

    【讨论】:

    • 谢谢!我对此进行了测试,并且可以正常工作!有没有办法以更标准的方式获得这种行为,即不启用扩展?
    • 您误解了扩展。它们不是黑客,它们是语言的一部分,只能独立选择。
    • @J.AntonioPerez 您必须首先启用扩展以允许多参数类型类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-12
    • 2013-01-28
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多