Читатель 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 (++ ".")

2 ответа

Решение

Reader не часто используется сам по себе в реальном коде. Как вы заметили, на самом деле это не лучше, чем просто передать дополнительный аргумент вашим функциям. Однако, как часть монадного преобразователя, это отличный способ передать параметры конфигурации через ваше приложение. Обычно это делается путем добавления 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

dfeuer, chi и user2297560 правы в этом Reader не часто используется сам по себе в реальном коде ". Однако стоит отметить, что между тем, что вы делаете во втором фрагменте вопроса, и тем, Reader как монада: функтор функции просто Reader без обертки, а Monad а также Applicativeэкземпляры для них обоих эквивалентны. Кстати, вне высокополиморфного кода1, типичная мотивация для использования функции Applicative делает код более бессмысленным. В этом случае умеренность очень рекомендуется. Например, что касается моего собственного вкуса, это...

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

... хорошо (а иногда это может даже читать лучше, чем многозначительное написание), в то время как это...

end <*> greet "James"

... просто сбивает с толку.


Сноски

  1. Например, как указывает Карл в комментарии, он и связанные с ним экземпляры могут быть полезны в...

    [...] места, где у вас есть код, который полиморфен в конструкторе типов, и ваш вариант использования передает аргумент. Это может возникнуть, например, при использовании полиморфных типов, предлагаемых линзами.

Другие вопросы по тегам