Ответ 1
В выражении типа 2 3 4
с вашими экземплярами функции 2
и 3
являются функциями. Итак, 2
на самом деле (2 *)
и имеет тип Num a => a -> a
. 3
- то же самое. 2 3
тогда (2 *) (3 *)
, что совпадает с 2 * (3 *)
. По вашему экземпляру это liftM2 (*) 2 (3 *)
, который затем liftM2 (*) (2 *) (3 *)
. Теперь это выражение работает без каких-либо ваших экземпляров.
Так что это значит? Ну, liftM2
для функций является своего рода двойной композицией. В частности, liftM2 f g h
совпадает с \ x -> f (g x) (h x)
. Итак, liftM2 (*) (2 *) (3 *)
тогда \ x -> (*) ((2 *) x) ((3 *) x)
. Упрощаем немного, получаем: \ x -> (2 * x) * (3 * x)
. Итак, теперь мы знаем, что 2 3 4
на самом деле (2 * 4) * (3 * 4)
.
Итак, почему liftM2
для функций работает таким образом? Давайте посмотрим на экземпляр monad для (->) r
(имейте в виду, что (->) r
есть (r ->)
, но мы не можем писать разделы оператора уровня):
instance Monad ((->) r) where
return x = \_ -> x
h >>= f = \w -> f (h w) w
So return
- const
. >>=
немного странно. Я думаю, что это легче увидеть в терминах join
. Для функций join
работает следующим образом:
join f = \ x -> f x x
То есть, он принимает функцию двух аргументов и превращает ее в функцию одного аргумента, используя этот аргумент дважды. Достаточно просто. Это определение также имеет смысл. Для функций join
должен превратить функцию двух аргументов в функцию одного; единственный разумный способ сделать это - дважды использовать этот один аргумент.
>>=
есть fmap
, за которым следует join
. Для функций fmap
является просто (.)
. Итак, теперь >>=
равно:
h >>= f = join (f . h)
который справедлив:
h >>= f = \ x -> (f . h) x x
теперь мы просто избавляемся от .
, чтобы получить:
h >>= f = \ x -> f (h x) x
Итак, теперь, когда мы знаем, как работает >>=
, мы можем посмотреть liftM2
. liftM2
определяется следующим образом:
liftM2 f a b = a >>= \ a' -> b >>= \ b' -> return (f a' b')
Мы можем просто понемногу. Во-первых, return (f a' b')
превращается в \ _ -> f a' b'
. В сочетании с \ b' ->
получаем: \ b' _ -> f a' b'
. Тогда b >>= \ b' _ -> f a' b'
превращается в:
\ x -> (\ b' _ -> f a' b') (b x) x
так как второй x
игнорируется, получаем: \ x -> (\ b' -> f a' b') (b x)
, который затем сводится к \ x -> f a' (b x)
. Таким образом, это оставляет нам:
a >>= \ a' -> \ x -> f a' (b x)
Снова подставим >>=
:
\ y -> (\ a' x -> f a' (b x)) (a y) y
это сводится к:
\ y -> f (a y) (b y)
что мы использовали как liftM2
раньше!
Надеюсь, теперь поведение 2 3 4
имеет смысл полностью.