【问题标题】:Understanding (->) r as instance of Reader [duplicate]将 (->) r 理解为 Reader 的实例 [重复]
【发布时间】:2015-12-27 15:57:50
【问题描述】:

所以我被告知 (->) r 是 Reader monad 的一个实例,但我似乎找不到任何具体的例子来说明它应该如何工作。我想使用它而不必在阅读器中显式包装我的一些代码

import Control.Monad.Reader

testOne :: Reader String String
testOne = do
  env <- ask
  return $ "Hello, " ++ env

testTwo :: String -> String
testTwo = do
  env <- ask
  return $ "G'day, " ++ env

运行runReader testOne "there" 工作正常,但运行runReader testTwo "mate" 失败并显示以下消息:

Couldn't match type ‘String -> String’
                   with ‘ReaderT [Char] Data.Functor.Identity.Identity a’
    Expected type: Reader [Char] a
      Actual type: String -> String

那么我在这里错过了什么?

【问题讨论】:

  • 为什么你想要 runReader testTwo "mate" 成为生成任何东西的有效语法,不同于id testTwo "mate"?请注意,(-&gt;) r 不是“Reader monad 的实例”,而是“与 Reader monad 做同样事情的 monad”。但是为了帮助您,我们需要知道:您是否正在与将Reader r x 作为参数的东西进行交互?或者您是否需要多态代码并且都接受Reader r xr -&gt; x 函数?还是什么?
  • @CRDrost 这更像是一个“如果我有同时使用这两种东西的东西”的问题,拥有一个统一的跑步者会很有用。但我想既然整个问题都是由误导性陈述引导的,我真的不需要它吗?

标签: haskell monads reader-monad


【解决方案1】:

runReader 的类型是runReader :: ReaderT r Identity -&gt; r -&gt; a,如果你展开newtype Reader = ReaderT r Identity。我认为你想要一些非常通用的东西,大致如下:

foo :: (MonadReader r m) => m a -> r -> a

这样您就可以同时评估foo testOne "there"foo testTwo "mate"

不幸的是,不存在这样的功能。 mtl 库的工作是抽象底层具体类型的选择。 (-&gt;) StringReader String = ReaderT String Identity 都是遵守 MonadMonadReader 法则的具体类型,但这只能保证您的接口为 return&gt;&gt;=askreader 和 @987654336 @(和&lt;$&gt;&lt;*&gt;pure)。

这既限制又有用!

限制:为了“运行”任一类型表示的计算,您需要使用适当的特定于类型的 API。对于(-&gt;) String,这只是调用函数(不可见的 函数应用运算符);对于Reader String,那就是runReader

有用:您可以通过库公开受MonadReader 约束的值,知道用户只能通过MonadReader 接口使用它们。这很好,因为您可以使用这个技巧来确保用户不会做任何不妥当的事情,例如在他们自己的环境 (r's) 中提前运行您的值。

【讨论】:

    【解决方案2】:

    runReader :: Reader r a -&gt; r -&gt; a 专门用于 Reader 新类型,这是您要避免的。由于testTwo 只是一个函数,因此您只需使用testTwo "mate"

    如果您想要一种通用的方式来运行MonadReader,您可以为此定义自己的类型类。大概是这样(未经测试):

    class MonadReader r m => RunReader r m | m -> r where
      type Output m a :: *
      runReader' :: m a -> r -> Output m a
    
    instance RunReader r ((->) r) where
      type Output ((->) r) a = a
      runReader' = ($)
    
    instance Monad m => RunReader r (ReaderT r m) where
      type Output (ReaderT r m) a = m a
      runReader' = runReaderT
    
    instance RunReader r m => RunReader r (MaybeT m) where
      type Output (MaybeT m) a = Output m (Maybe a)
      runReader' = runMaybeT . runReader'
    
    -- any other instances
    

    然后runReader' testOnerunReader' testTwo 将起作用。在此处查看"Associated data and type families" 以了解type 的使用说明。

    【讨论】:

    • 将其用作函数会破坏我想要完成的目的
    • 可能谁告诉你“(->) r 是 Reader monad 的一个实例”意味着它是 MonadReader 的一个实例(或者,他们可能意味着它与 Reader 同构类型)。所以你可以用相同的定义写testAbstract :: MonadReader m =&gt; m String String。但是没有通用的方法来“运行”MonadReader
    • 我已经扩展了答案以展示您如何创建这样的抽象。
    • 真的会接受那些Output 实例吗?他们不会占用太多索引吗?还是我很困惑?
    • @dfeuer 注意Output m 的类型。你也可以写成type Output m a :: *。链接文档中有一个与data GMap k 类似的示例。
    猜你喜欢
    • 2016-12-26
    • 2023-03-24
    • 2021-08-06
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 2018-09-04
    • 2015-01-26
    • 2023-02-12
    相关资源
    最近更新 更多