如何编写调用runStateT或runReaderT的函数run?

J. *_*rez 2 monads haskell typeclass monad-transformers

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

鉴于run s

  • 如果sStateTrun = runStateT
  • 如果sReaderTrun = runReaderT
  • 如果sMaybeTrun = runMaybeT

我试着创建一个typeclass 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 Code Online (Sandbox Code Playgroud)

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

simpleReader = ReaderT $ \env -> Just 10

runReaderT simpleReader ()
Run Code Online (Sandbox Code Playgroud)

此输出Just 10与预期的一样。

但是,当我尝试使用时run,它给了我一个错误:

run simpleReader ()
Run Code Online (Sandbox Code Playgroud)
<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
Run Code Online (Sandbox Code Playgroud)

如果我按照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 Code Online (Sandbox Code Playgroud)

Fyo*_*kin 6

简短的答案:您需要对类进行功能依赖。

长答案

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

编译器将您b用作函数,并将()其作为参数传递给您。因此,认为编译器,b必须是shape () -> tt尚不为人所知。

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

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

但是有一种方法可以向编译器解释。它被称为“功能依赖”,其编写方式如下:

class Runnable a b | a -> b where
Run Code Online (Sandbox Code Playgroud)

该符号a -> b告诉编译器b应该由明确确定a。这意味着,一方面,编译器将不允许您定义违反此规则的实例,另一方面,编译器将Runnable仅通过知道就能找到合适的实例a,然后b从该实例中进行确定。


HTN*_*TNW 5

的输出类型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
Run Code Online (Sandbox Code Playgroud)

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

act :: ReaderT Env Identity Ret
Run Code Online (Sandbox Code Playgroud)

然后

run act :: Runnable (ReaderT Env Identity Ret) b => b
Run Code Online (Sandbox Code Playgroud)

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

instance Runnable (ReaderT r m a) (ReaderT r m a) where
  run = id
Run Code Online (Sandbox Code Playgroud)

现在run act可以是ReaderT Env Identity RetEnv -> Identity Ret。依赖a -> b一)允许我们推断b来自a和b)限制instance的声明使这成为可能。像我给的那样有冲突的实例将被拒绝,并且根据需要run act具有通过查看您给的实例b推断出的类型。Env -> Identity Retinstance

  • @ J.AntonioPerez您必须启用扩展才能首先允许多参数类型类。 (5认同)
  • 您误解了扩展名。它们不是hack,它们是语言的一部分,只能独立选择。 (2认同)