Capisco come ti senti. Ho trovato la composizione della funzione abbastanza difficile da afferrare in un primo momento. Ciò che mi ha aiutato ad aggirare la questione sono state le firme di tipo. Si consideri:
(*) :: Num x => x -> x -> x
(+) :: Num y => y -> y -> y
(.) :: (b -> c) -> (a -> b) -> a -> c
Ora, quando si scrive (*) . (+)
in realtà è lo stesso di (.) (*) (+)
(cioè (*)
è il primo argomento di (.)
e (+)
è il secondo argomento a (.)
):
(.) :: (b -> c) -> (a -> b) -> a -> c
|______| |______|
| |
(*) (+)
Quindi il tipo firma di (*)
(esNum x => x -> x -> x
) unifica con b -> c
:
(*) :: Num x => x -> x -> x -- remember that `x -> x -> x`
| |____| -- is implicitly `x -> (x -> x)`
| |
b -> c
(.) (*) :: (a -> b) -> a -> c
| |
| |‾‾‾‾|
Num x => x x -> x
(.) (*) :: Num x => (a -> x) -> a -> x -> x
qui la firma di tipo (+)
(cioè Num y => y -> y -> y
) unifica con Num x => a -> x
:
(+) :: Num y => y -> y -> y -- remember that `y -> y -> y`
| |____| -- is implicitly `y -> (y -> y)`
| |
Num x => a -> x
(.) (*) (+) :: Num x => a -> x -> x
| | |
| |‾‾‾‾| |‾‾‾‾|
Num y => y y -> y y -> y
(.) (*) (+) :: (Num (y -> y), Num y) => y -> (y -> y) -> y -> y
Spero che chiarisce dove il Num (y -> y)
e Num y
provengono. Ti rimane una funzione molto strana del tipo (Num (y -> y), Num y) => y -> (y -> y) -> y -> y
.
Ciò che lo rende così strano è che si aspetta che sia e y -> y
siano istanze di Num
. È comprensibile che debba essere un'istanza di Num
, ma come y -> y
? Creare y -> y
un'istanza di Num
sembra illogico. Questo non può essere corretto.
Tuttavia, ha senso quando si guarda a ciò che realmente fa la composizione funzione:
(f . g) = \z -> f (g z)
((*) . (+)) = \z -> (*) ((+) z)
in modo da avere una funzione \z -> (*) ((+) z)
. Quindi, z
deve essere chiaramente un'istanza di Num
perché a esso viene applicato (+)
. Quindi il tipo di \z -> (*) ((+) z)
è Num t => t -> ...
dove ...
è il tipo di (*) ((+) z)
, che troveremo in un momento.
Pertanto ((+) z)
è del tipo Num t => t -> t
perché richiede un altro numero. Tuttavia, prima che venga applicato a un altro numero, viene applicato (*)
.
Quindi (*)
aspetta ((+) z)
essere un'istanza di Num
, motivo per cui t -> t
dovrebbe essere un'istanza di Num
. Pertanto, lo ...
viene sostituito da (t -> t) -> t -> t
e il vincolo Num (t -> t)
viene aggiunto, risultando nel tipo (Num (t -> t), Num t) => t -> (t -> t) -> t -> t
.
Il modo in cui si vuole veramente coniugare (*)
e (+)
sta usando (.:)
:
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
f .: g = \x y -> f (g x y)
Quindi (*) .: (+)
è lo stesso di \x y -> (*) ((+) x y)
. Ora vengono forniti due argomenti a (+)
assicurando che ((+) x y)
sia effettivamente solo Num t => t
e non Num t => t -> t
.
Quindi ((*) .: (+)) 2 3 5
è (*) ((+) 2 3) 5
che è (*) 5 5
che è 25
, che credo sia quello che vuoi.
Nota che f .: g
può anche essere scritto come (f .) . g
e (.:)
può anche essere definito come (.:) = (.) . (.)
. Si può leggere di più su di esso qui:
What does (f .) . g mean in Haskell?
Speranza che aiuta.
Grazie per la vostra risposta espansiva. Sto provando ad attraversare ogni parte di esso e capire. Ad un certo punto si cita '(.) (*) :: (a -> b) -> a -> c', che diventa' Num x => (a -> x) -> a -> x -> x '. Da dove viene l'extra 'x'? Perché 'c' diventa' x -> x', e non solo 'x'? – user2666425
@ user2666425 La freccia della funzione è associativa corretta. Ciò significa che le funzioni del tipo 'f :: a -> b -> c' sono * più esplicitamente * del tipo' f :: a -> (b -> c) '. Ciò segue l'intuizione che 'f' è una funzione che prende un argomento del tipo' a' e restituisce un'altra funzione 'g :: b -> c', che a sua volta prende un argomento del tipo' b' e restituisce un valore del tipo 'c'. Quindi quando unifiamo '(*) :: Num x => x -> x -> x' con' b -> c' stiamo naturalmente unificando 'Num x => x -> (x -> x)' con ' b -> c'. Quindi 'b' si unifica con' x' e 'c' si unifica con' x -> x'. Spero possa aiutare. =) –
Se vuoi capire come funziona l'unificazione, allora questa risposta potrebbe aiutarti: http://stackoverflow.com/a/27117861/783743 –