【问题标题】:Simplifying the invocation of functions stored inside an ReaderT environment简化存储在 ReaderT 环境中的函数的调用
【发布时间】:2020-05-06 18:22:57
【问题描述】:

假设我有这样的环境记录:

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

我想在RIO Env monad 中以简洁的方式调用这些函数。

我可以写这样的call函数:

import Control.Monad.Reader 

call :: MonadReader env m => (env -> f) -> (f -> m r) -> m r
call getter execute = do
  f <- asks getter
  execute f

并像这样使用它(可能与-XBlockArguments结合使用):

 example1 :: RIO Env ()
 example1 = call foo $ \f -> f 0 "fooarg"

但是,理想情况下,我希望有一个 call 版本,它允许以下更直接的语法,并且仍然适用于具有不同数量参数的函数:

 example2 :: RIO Env ()
 example2 = call foo 0 "fooarg"

 example3 :: RIO Env Int
 example3 = call bar 3

这可能吗?

【问题讨论】:

    标签: haskell rio


    【解决方案1】:

    从这两个例子中,我们可以猜测call 的类型是(Env -&gt; r) -&gt; r

    example2 :: RIO Env ()
    example2 = call foo 0 "fooarg"
    
    example3 :: RIO Env Int
    example3 = call bar 3
    

    把它放在一个类型类中,考虑两种情况,r 是一个箭头a -&gt; r',或者r 是一个RIO Env r'。使用类型类实现可变参数通常不受欢迎,因为它们非常脆弱,但它在这里工作得很好,因为RIO 类型提供了一个自然的基本情况,并且一切都由访问器的类型指导(所以类型推断不是顺便)。

    class Call r where
      call :: (Env -> r) -> r
    
    instance Call r => Call (a -> r) where
      call f x = call (\env -> f env x)
    
    instance Call (RIO Env r') where
      call f = ask >>= f
    

    【讨论】:

    • 您可以避免使用FlexibleInstances,并可能通过使用instance e ~ Env =&gt; Call (RIO e r) 来改进键入的漏洞消息。
    • 或者更好,请使用FlexibleInstances,但要改进课程。 class e ~ TheEnv r =&gt; Call e r where type TheEnv r; call :: (e -&gt; r) -&gt; r,等等。 IO 也不必修复; ReaderT e ' m rMonad m 实例约束配合得很好。
    • 我在 github.com/danidiaz/moo-nad987654321@
    • 相关功能明珠:“Haskell 中的装饰器模式”web.cecs.pdx.edu/~ntc2/haskell-decorator-paper.pdf
    【解决方案2】:

    以下是对李耀的回答的一些小改进。此版本不特定于 IO 作为基本 monad,或特定于 Env 作为环境类型。在基本案例实例中使用等式约束应该会稍微改进类型推断,尽管 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
    

    【讨论】:

      猜你喜欢
      • 2017-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 2014-07-16
      • 1970-01-01
      • 2016-09-30
      • 2021-04-19
      相关资源
      最近更新 更多