J. *_*rez 2 monads haskell typeclass monad-transformers
如何编写一个通用函数run,该通用函数接受某个monad转换器的对象,然后调用相应的函数?
鉴于run s,
s是StateT,run = runStateTs是ReaderT,run = runReaderTs是MaybeT,run = 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)
简短的答案:您需要对类进行功能依赖。
长答案:
当编译器看到时run,它需要找到的适当实例Runnable以确定run要使用哪个实现。为了找到该实例,它需要知道什么a和b是什么。它知道a是ReaderT,所以覆盖了一个。但是什么b呢?
编译器将您b用作函数,并将()其作为参数传递给您。因此,认为编译器,b必须是shape () -> t,t尚不为人所知。
这就是停止的地方:编译器无处可寻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从该实例中进行确定。
的输出类型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 Ret或Env -> Identity Ret。依赖a -> b一)允许我们推断b来自a和b)限制instance的声明使这成为可能。像我给的那样有冲突的实例将被拒绝,并且根据需要run act具有通过查看您给的实例b推断出的类型。Env -> Identity Retinstance