Reader Monad - 简单案例的解释

Reader Monad - explanation of trivial case

我一直在尝试掌握 reader monad 并遇到了 this tutorial。在其中,作者给出了这个例子:

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 (++ ".")

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

这是一个更 real-world 示例的尝试:

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

dfeuer、chi 和 user2297560 在这一点上是正确的 . It is worth noting, though, that there is next to no essential difference between what you do in the second snippet in the question and actually using Reader as a monad: the function functor is just Reader without the wrappers, and 。顺便说一下,除了高度多态代码 1,使用函数 Applicative 的典型动机是让代码更无意义。在这种情况下,适度是非常可取的。例如,就我自己的口味而言,这个...

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

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

end <*> greet "James"

...只是令人困惑。


脚注

  1. 例如,,它和相关实例可用于...

    [...] places where you have code that's polymorphic in a type constructor and your use case is passing an argument in. This can come up when using the polymorphic types offered by lenses, for instance.