Haskell 简化ReaderT环境中存储的函数的调用
假设我有这样一个环境记录:Haskell 简化ReaderT环境中存储的函数的调用,haskell,rio,Haskell,Rio,假设我有这样一个环境记录: import Control.Monad.IO.Class import Control.Monad.Trans.Reader type RIO env a = ReaderT env IO a data Env = Env { foo :: Int -> String -> RIO Env (), bar :: Int -> RIO Env Int } env :: Env env = Env { foo = \_
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
type RIO env a = ReaderT env IO a
data Env = Env
{ foo :: Int -> String -> RIO Env (),
bar :: Int -> RIO Env Int
}
env :: Env
env =
Env
{ foo = \_ _ -> do
liftIO $ putStrLn "foo",
bar = \_ -> do
liftIO $ putStrLn "bar"
return 5
}
存储在环境中的函数可能有不同数量的参数,但它们总是在RIO Env
monad中生成值,即在ReaderT
overIO
中由环境本身参数化
我希望在RIO Env
monad中使用一种简洁的方法来调用这些函数
我可以编写这样的调用函数:
import Control.Monad.Reader
call :: MonadReader env m => (env -> f) -> (f -> m r) -> m r
call getter execute = do
f <- asks getter
execute f
但是,理想情况下,我希望有一个版本的call
,它允许以下更直接的语法,并且仍然适用于具有不同数量参数的函数:
example2 :: RIO Env ()
example2 = call foo 0 "fooarg"
example3 :: RIO Env Int
example3 = call bar 3
这可能吗?从这两个例子中,我们可以猜测调用
的类型是(Env->r)->r
example2::里约环境()
示例2=调用foo 0“fooarg”
示例3::里约环境内部
示例3=呼叫栏3
将其放入类型类中,并考虑两种情况,<代码> R>代码>是箭头<代码> A- > R′<代码>,或<代码> R>代码>是<代码> Rio Env R '/Cuth>。使用类型类实现变量通常是不受欢迎的,因为它们是多么脆弱,但是在这里它工作得很好,因为
RIO
类型提供了一个自然的基本情况,并且所有事情都是由访问器的类型来指导的(因此类型推断不起作用)
类调用r其中
调用::(环境->r)->r
实例调用r=>Call(a->r)其中
调用fx=调用(\env->f env x)
实例调用(RIO Env r'),其中
呼叫f=询问>>=f
以下是对李耀答案的一些小改进。此版本不特定于将IO
作为基本monad,或将Env
作为环境类型。在基本事例实例中使用相等约束应该会稍微改进类型推断,尽管ascall
的使用可能只会影响类型化孔
{-# language MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}
class e ~ TheEnv r => Call e r where
type TheEnv r
call :: (e -> r) -> r
instance Call e r => Call e (a -> r) where
type TheEnv (a -> r) = TheEnv r
call f x = call (\env -> f env x)
instance (Monad m, e ~ e') => Call e (ReaderT e' m r) where
type TheEnv (ReaderT e' m r) = e'
call f = ask >>= f
关联的类型可以说是过度杀戮。也可以使用函数依赖项:
{-# language FunctionalDependencies, TypeFamilies, FlexibleInstances, UndecidableInstances #-}
class Call e r | r -> e where
call :: (e -> r) -> r
instance Call e r => Call e (a -> r) where
call f x = call (\env -> f env x)
instance (Monad m, e ~ e') => Call e (ReaderT e' m r) where
call f = ask >>= f
您可以使用instance e~Env=>Call(RIO e r)
避免FlexibleInstances
并可能改进键入的孔消息。或者更好的方法是使用FlexibleInstances
但改进类<代码>类e~TheEnv r=>调用e r,其中键入TheEnv r;调用::(e->r)->r
,依此类推<代码>IO
也不必固定ReaderT e'm r
可以很好地处理Monad m
实例约束。我在这里对这个解决方案做了更多的尝试,它与函数pearl相关:“Haskell中的Decorator模式”
{-# language FunctionalDependencies, TypeFamilies, FlexibleInstances, UndecidableInstances #-}
class Call e r | r -> e where
call :: (e -> r) -> r
instance Call e r => Call e (a -> r) where
call f x = call (\env -> f env x)
instance (Monad m, e ~ e') => Call e (ReaderT e' m r) where
call f = ask >>= f