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

person matt    schedule 26.03.2018    source источник
comment
Не все, что вы найдете в Интернете, лучше, чем все, что вы пишете сами.   -  person dfeuer    schedule 26.03.2018
comment
Я не думаю, что пример предназначен для того, чтобы показать полезность монады чтения (которая не так уж и велика), а показать, как использовать ask, asks.   -  person chi    schedule 26.03.2018
comment
@dfeuer, так что я предполагаю, что попытка написать функцию так, как я, была бы лучшим вариантом.   -  person matt    schedule 26.03.2018
comment
@chi монада для чтения действительно не так уж полезна? Я постоянно слышу, что это.. :(   -  person matt    schedule 26.03.2018
comment
@matthias, читательская монада transformer гораздо полезнее. В вашей версии вы должны либо отказаться от аргумента <*> business, либо eta уменьшить context.   -  person dfeuer    schedule 26.03.2018
comment
Это то, чему следует научиться — вы не тратите свое время понапрасну. Однако редко (IMO) можно увидеть, как монада чтения используется сама по себе. Обычно используется преобразователь считывателя (как упоминает dfeuer) или какой-либо другой преобразователь, созданный с помощью монады считывателя.   -  person chi    schedule 26.03.2018
comment
Есть случаи, когда Reader (или экземпляр монады для ((-›) r)) полезен в реальном мире, но это случаи, когда у вас есть код, полиморфный в конструкторе типа, и ваш вариант использования передает аргумент в Это может произойти, например, при использовании полиморфных типов линз.   -  person Carl    schedule 26.03.2018


Ответы (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
person user2297560    schedule 26.03.2018

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

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

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

end <*> greet "James"

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


Сноски

  1. #P5# <блочная цитата> #P6#
person duplode    schedule 26.03.2018
comment
возможно, end <*> greet "James" сбивает с толку из-за имен функций ... может быть, что-то вроде addEnding <*> great "James" было бы меньше? - person matt; 27.03.2018
comment
[1/2] @matthias Лучше, но ненамного, я бы сказал. Дело в том, что (<*>) для функций заключается в том, что (1) за его компактным внешним видом скрывается довольно много всего, что связано только с приложением функций (g <*> f = \x -> g x (f x)); и (2) использование экземпляра функции Applicative обычно застает людей врасплох. - person duplode; 27.03.2018
comment
[2/2] @matthias Все это немного субъективно, но эти проблемы смягчаются в (&&) <$> isFoo <*> isBar (или liftA2 (&&) isFoo isBar, что мне нравится немного меньше в конкретном случае функций) знакомой формой выражения аппликативного стиля, а также isFoo и isBar явно выглядят как функции с одним аргументом, а (&&) явно является функцией с двумя аргументами — напротив, арность greet "James" сразу не очевидна. Эти факторы облегчают распознавание того, что функция Applicative используется. - person duplode; 27.03.2018
comment
спасибо, но не могли бы вы привести пример того, как бы вы написали это сами? - person matt; 27.03.2018
comment
@matthias По-старому: \input -> addEnding input (greet "James" input). Хотя мне нравится использовать возможности для пропуска именованных аргументов, я не чувствую, что использование (<*>) для этого окупается в этом случае. - person duplode; 27.03.2018