Ответ 1
Хорошо, поэтому у вас есть этот тип
data Const a b = Const { getConst :: a }
Ваш первый вопрос: "Откуда приходит b
?"
Ответ заключается в том, что он не приходит нигде. Точно так же, как вы можете думать о Maybe b
как контейнер, который содержит либо 0, либо 1 значение типа b
, a Const a b
- это контейнер, который содержит ровно 0 значений типа b
(но определенно выполняется значение типа a
).
Ваш второй вопрос: "Почему он там?"
Хорошо, иногда полезно иметь функтор, который говорит, что он может содержать значения типа b
, но на самом деле имеет что-то другое (например, думать о функторе Either a b
), разница в том, что Either a b
может содержать значение типа b
, тогда как Const a b
определенно не соответствует).
Затем вы спросили о фрагментах кода pure id <*> Const "hello"
и Const id <*> Const "hello"
. Вы думали, что это одно и то же, но это не так. Причина в том, что экземпляр Applicative
для Const
выглядит как
instance Monoid m => Applicative (Const m) where
-- pure :: a -> Const m a
pure _ = Const mempty
-- <*> :: Const m (a -> b) -> Const m a -> Const m b
Const m1 <*> Const m2 = Const (m1 <> m2)
Поскольку на самом деле нет значений, имеющих тип второго параметра, нам нужно иметь дело только с теми, у кого есть тип первого параметра, который, как мы знаем, является моноидом. Вот почему мы можем сделать Const
экземпляр Applicative
- нам нужно вытащить значение типа m
откуда-то, а экземпляр Monoid
дает нам способ сделать его из ниоткуда (используя mempty
).
Итак, что происходит в ваших примерах? У вас есть pure id <*> Const "hello"
, который должен иметь тип Const String a
с id :: a -> a
. Моноид в этом случае равен String
. Имеем mempty = ""
для a String
и (<>) = (++)
. Таким образом, вы получаете
pure id <*> Const "hello" = Const "" <*> Const "hello"
= Const ("" <> "hello")
= Const ("" ++ "hello")
= Const "hello"
С другой стороны, когда вы пишете Const id <*> Const "hello"
, левый аргумент имеет тип Const (a -> a) b
, а правый имеет тип Const String b
, и вы видите, что типы не совпадают, поэтому вы получаете тип ошибка.
Теперь, почему это когда-нибудь полезно? Одно приложение находится в библиотеке lens, которая позволяет использовать геттеры и сеттеры (знакомые по императивному программированию) в чистой функциональной настройке. Простое определение объектива
type Lens b a = forall f. Functor f => (a -> f a) -> (b -> f b)
то есть. если вы дадите ему функцию, которая преобразует значения типа a
, она вернет вам функцию, которая преобразует значения типа b
. Для чего это полезно? Итак, возьмем случайную функцию типа a -> f a
для конкретного функтора f
. Если мы выберем функтор Identity
, который выглядит как
data Identity a = Identity { getIdentity :: a }
то если l
является линзой, определение
modify :: Lens b a -> (a -> a) -> (b -> b)
modify l f = runIdentity . l (Identity . f)
предоставляет вам способ выполнения функций, которые преобразуют a
и превращают их в функции, преобразующие b
s.
Другая функция типа a -> f a
, которую мы могли бы передать, - Const :: a -> Const a a
(обратите внимание, что мы специализируемся, чтобы второй тип был таким же, как и первый). Тогда действие линзы l
состоит в том, чтобы превратить ее в функцию типа b -> Const a b
, которая говорит нам, что она может содержать b
, но на самом деле она скрытно содержит a
! Как только мы применили его к типу b
, чтобы получить Const a b
, мы можем нажать его с помощью getConst :: Const a b -> a
, чтобы вытащить значение типа a
из шляпы. Таким образом, это дает нам способ извлечь значения типа a
из a b
- i.e это геттер. Определение выглядит как
get :: Lens b a -> b -> a
get l = getConst . l Const
В качестве примера объектива вы можете определить
first :: Lens (a,b) a
first f (a,b) = fmap (\x -> (x,b)) (f a)
чтобы вы могли открыть сеанс GHCI и написать
>> get first (1,2)
1
>> modify first (*2) (3,4)
(6,4)
который, как вы можете себе представить, полезен во всех ситуациях.