2012-08-26 11 views
17

w komentarzach pytanie Tacit function composition in Haskell, osoby wymienionej dokonywania wystąpienie Num dla a -> r, więc pomyślałem, że mogę grać z użyciem notacji funkcyjnego reprezentuje mnożenie:liczb jako funkcji multyplikatywnych (dziwne, ale zabawny)

{-# 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 *) 

Należy pamiętać, że definicja fromInteger oznacza, że ​​mogę napisać 3 4, która ocenia do 12, a 7 (2+8) ma 70, tak jak można by oczekiwać.

To wszystko idzie cudownie, zabawnie dziwnie! Proszę wyjaśnić ten wierdness jeśli można:

*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 

[Edit. Stosowane zamiast aplikacyjnych Monady ponieważ aplikacyjnych jest ogólnie super, ale to nie robi dużej różnicy w ogóle do kodu]

+2

W GHC 7.4 możliwe jest usunięcie manekinów 'Show' i' Eq', ponieważ 'Num' już ich nie wymaga. – sdcvvc

+3

'Monada' to przesada tutaj. Prostszy i bardziej ogólny "Applicative" wystarcza. – Conal

+0

@sdcvvc Niedługo uaktualnię, tak. – AndrewC

Odpowiedz

21

W wyrażenie takie jak 2 3 4 z Twoimi wystąpieniami, zarówno 2 jak i 3 są funkcjami. Tak więc 2 jest rzeczywiście (2 *) i ma typ Num a => a -> a. 3 jest taki sam. 2 3 to następnie (2 *) (3 *), który jest taki sam jak 2 * (3 *). Według Twojej instancji jest to liftM2 (*) 2 (3 *), która jest następnie liftM2 (*) (2 *) (3 *). Teraz to wyrażenie działa bez żadnych instancji.

Co to oznacza? Cóż, liftM2 dla funkcji jest rodzajem podwójnej kompozycji. W szczególności liftM2 f g h jest taki sam jak \ x -> f (g x) (h x). Tak więc liftM2 (*) (2 *) (3 *) jest następnie \ x -> (*) ((2 *) x) ((3 *) x). Upraszczając nieco, otrzymujemy: \ x -> (2 * x) * (3 * x). Teraz wiemy, że 2 3 4 jest w rzeczywistości (2 * 4) * (3 * 4).

Teraz, dlaczego funkcje liftM2 działają w ten sposób? Spójrzmy na przykład na (->) r monada (należy pamiętać, że (->) r jest (r ->) ale nie możemy napisać sekcje operatora typu poziom):

instance Monad ((->) r) where 
    return x = \_ -> x 
    h >>= f = \w -> f (h w) w 

Więc return jest const. >>= jest trochę dziwne. Myślę, że łatwiej to zobaczyć pod względem join. Dla funkcji, join działa tak:

join f = \ x -> f x x 

Oznacza to, że trwa funkcji dwóch argumentów i zamienia ją w zależności od jednego argumentu za pomocą tego argumentu dwukrotnie. Wystarczająco proste. Ta definicja ma również sens. W przypadku funkcji, join musi zamienić funkcję dwóch argumentów na funkcję jednego; jedynym rozsądnym sposobem, aby to zrobić, jest użycie tego jednego argumentu dwa razy.

>>= to , po którym następuje join. W przypadku funkcji fmap jest po prostu (.).Więc teraz >>= jest równa:

h >>= f = join (f . h) 

który jest po prostu:

h >>= f = \ x -> (f . h) x x 

teraz po prostu pozbyć . dostać:

h >>= f = \ x -> f (h x) x 

Więc teraz, że wiemy, jak >>= prace , możemy spojrzeć na liftM2. liftM2 jest zdefiniowany w następujący sposób:

liftM2 f a b = a >>= \ a' -> b >>= \ b' -> return (f a' b') 

Możemy to zrobić po trochu. Najpierw return (f a' b') zmienia się w \ _ -> f a' b'. W połączeniu z \ b' -> otrzymujemy: \ b' _ -> f a' b'. Następnie b >>= \ b' _ -> f a' b' zamienia:

\ x -> (\ b' _ -> f a' b') (b x) x 

od drugiej x jest ignorowany, otrzymujemy: \ x -> (\ b' -> f a' b') (b x) który jest następnie redukowany do \ x -> f a' (b x). Więc to pozostawia nam:

a >>= \ a' -> \ x -> f a' (b x) 

ponownie, zastąpić >>=:

\ y -> (\ a' x -> f a' (b x)) (a y) y 

ten sprowadza się do:

\ y -> f (a y) (b y) 

który jest dokładnie to, czego wykorzystywane jako liftM2 wcześniej!

Mam nadzieję, że teraz zachowanie 2 3 4 ma sens całkowicie.

+0

Ach tak - jak tylko osiągnąłeś 'liftM2 (*) (2 *) (3 *) 4' zobaczyłem, dlaczego to był kwadratowy ostatni argument - to po prostu oznacza' (+) $ (2 *) 4 $ (3 *) 4'. A '(2 3) (5 2)' ma niepotrzebne nawiasy, więc jest to po prostu '2 4 (5 2)' i wynosi 300 z tego samego powodu. – AndrewC

+0

Nawiasem mówiąc, jestem zadowolony z instancji Applicative i Monad dla '(->) r' i powinienem to powiedzieć w pytaniu, to tylko mój mózg właśnie zaczął wyciekać z moich uszu, gdy robiłem" 2 3 4 ', a nawet nie próbowałam dokonać ręcznej oceny. doh! Twoje wyjaśnienie sprawi, że dla innych będzie to znacznie jaśniejsze, więc dziękuję. – AndrewC

+2

@AndrewC: Właśnie zapisałem swój proces myślowego od momentu, w którym dokładnie ustaliłem, co zrobiło "2 3 4", więc pomagałem sobie tak samo jak innym: P. –

Powiązane problemy