代码之家  ›  专栏  ›  技术社区  ›  danidiaz

简化ReaderT环境中存储的函数的调用

  •  0
  • danidiaz  · 技术社区  · 6 年前

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

    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 蒙纳德,也就是说,在 ReaderT 结束 IO 由环境本身参数化。

    我希望在 蒙纳德。

    我可以写这样的东西 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"
    

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

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

    可能吗?

    1 回复  |  直到 6 年前
        1
  •  3
  •   Li-yao Xia    6 年前

    从这两个例子中,我们可以猜到 call 我会打 (Env -> r) -> r .

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

    把它放在一个类型类中,考虑两种情况, r 这是一支箭 a -> 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
    
        2
  •  1
  •   dfeuer    6 年前

    以下是对李耀答案的一些小改进。这个版本不是特定于 IO 作为基础单子,或者 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
    
    推荐文章