Числа как мультипликативные функции (странные, но развлекательные)

В комментариях к вопросу Tacit function composition в Haskell люди упомянули создание Num экземпляр для a -> r, поэтому я подумал, что буду играть с использованием нотации функций для представления умножения:

{-# LANGUAGE TypeFamilies #-}
import Control.Applicative

instance Show (a->r) where   -- not needed in recent GHC versions
  show f = " a function "

instance Eq (a->r) where     -- not needed in recent GHC versions
  f == g = error "sorry, Haskell, I lied, I can't really compare functions for equality"

instance (Num r,a~r) => Num (a -> r) where
  (+) = liftA2 (+)
  (-) = liftA2 (-)
  (*) = liftA2 (*)
  abs = liftA abs
  negate = liftA negate
  signum = liftA signum
  fromInteger a = (fromInteger a *)

Обратите внимание, что определение fromInteger означает, что я могу написать 3 4, который оценивается в 12, а 7 (2+8) равен 70, как вы могли бы надеяться.

Тогда все идет чудесно, развлекательно странно! Пожалуйста, объясните эту странность, если сможете:

*Main> 1 2 3
18
*Main> 1 2 4
32
*Main> 1 2 5
50
*Main> 2 2 3
36
*Main> 2 2 4
64
*Main> 2 2 5
100
*Main> (2 3) (5 2)
600

[Изменить: используется Аппликация вместо Монады, потому что аппликация в целом хороша, но она не имеет большого значения для кода.]

Ответ 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 имеет смысл полностью.