2

我一直在努力掌握 reader monad 并遇到了本教程。在其中,作者提出了这个例子:

example2 :: String -> String
example2 context = runReader (greet "James" >>= end) context
    where
        greet :: String -> Reader String String
        greet name = do
            greeting <- ask
            return $ greeting ++ ", " ++ name

        end :: String -> Reader String String
        end input = do
            isHello <- asks (== "Hello")
            return $ input ++ if isHello then "!" else "."

我知道这是一个显示机制的简单示例,但我试图弄清楚为什么它比执行以下操作更好:

example3 :: String -> String
example3 = end <*> (greet "James")
  where
    greet name input = input ++ ", " ++ name
    end   input      = if input == "Hello" then (++ "!") else (++ ".")
4

2 回答 2

9

Reader在实际代码中并不经常单独使用。正如您所观察到的,这并不比仅向您的函数传递一个额外的参数更好。但是,作为 monad 转换器的一部分,它是通过应用程序传递配置参数的绝佳方式。通常这是通过MonadReader向任何需要访问配置的函数添加约束来完成的。

这是一个更真实的例子的尝试:

data Config = Config
  { databaseConnection :: Connection
  , ... other configuration stuff
  }

getUser :: (MonadReader Config m, MonadIO m) => UserKey -> m User
getUser x = do
   db <- asks databaseConnection
   .... fetch user from database using the connection

那么你main会看起来像:

main :: IO ()
main = do
  config <- .... create the configuration
  user <- runReaderT (getUser (UserKey 42)) config
  print user
于 2018-03-26T16:31:38.617 回答
1

dfeuer、chi 和 user2297560 是正确的,因为Reader在实际代码中并不经常单独使用”。不过,值得注意的是,您在问题的第二个片段中所做的操作与实际Reader用作 monad 之间几乎没有本质区别:函数仿函数只是Reader没有包装器,以及它们两者MonadandApplicative实例是等价的。顺便说一句,在高度多态的代码1之外,使用该函数的典型动机Applicative是使代码更加无点。在这种情况下,适度是非常可取的。例如,就我个人的口味而言,这...

(&&) <$> isFoo <*> isBar

...很好(有时它甚至可能比尖刻的拼写更好读),而这...

end <*> greet "James"

......只是令人困惑。


脚注

  1. 例如,正如卡尔在评论中指出的那样,它和相关的实例可以用于...

    [...] 在类型构造函数中有多态代码并且您的用例正在传入参数的地方。例如,在使用镜头提供的多态类型时会出现这种情况。

于 2018-03-26T19:17:26.023 回答