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
over
IO
中由环境本身参数化

我希望在
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
作为环境类型。在基本事例实例中使用相等约束应该会稍微改进类型推断,尽管as
call
的使用可能只会影响类型化孔

{-# 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