Ответ 1
Сначала это может быть немного запутанным, но важно помнить, что (->)
не является монадой или функтором, а (->) r
is. Monad
и Functor
все имеют вид * -> *
, поэтому они ожидают только одного параметра типа.
Это означает, что fmap
для (->) r
выглядит как
fmap g func = \x -> g (func x)
который также известен как
fmap g func = g . func
который является просто нормальной функциональной композицией! Когда вы fmap g
над func
, вы изменяете тип вывода, применяя к нему g
. В этом случае, если func
имеет тип a -> b
, g
должен иметь тип типа b -> c
.
Экземпляр Monad
более интересен. Он позволяет использовать результат применения приложения "до" этого приложения. То, что помогло мне понять, это увидеть пример вроде
f :: Double -> (Double,Double)
f = do
x1 <- (2*)
x2 <- (2+)
return (x1, x2)
> f 1.0
(2.0, 3.0)
Что это значит, применяет неявный аргумент к f
к каждой из функций в правой части привязок. Поэтому, если вы перейдете в 1.0
в f
, он привяжет значение 2 * 1.0
к x1
и привяжет 2 + 1.0
к x2
, а затем вернет (x1, x2)
. Это действительно упрощает применение одного аргумента ко многим подвыражениям. Эта функция эквивалентна
f' x = (2 * x, 2 + x)
Почему это полезно? Одним из распространенных способов использования является монада Reader
, которая представляет собой просто оболочку newtype вокруг (->) r
. Монада Reader
упрощает применение статической глобальной конфигурации в вашем приложении. Вы можете написать код, например
myApp :: Reader Config ()
myApp = do
config <- ask
-- Use config here
return ()
И затем вы запустите свое приложение с помощью runReader myApp initialConfig
. Вы можете легко записывать действия в монаде Reader Config
, составлять их, объединять их вместе, и все они имеют доступ к глобальной конфигурации readonly. Кроме того, есть компаньон ReaderT
monad transformer, который позволяет вам встраивать его в ваш стек трансформатора, позволяя вам иметь очень сложные приложения, которые имеют легкий доступ к статической конфигурации.