Lasciami riscrivere la e xample usando una notazione simile a System-F, in cui passiamo anche i tipi. Sotto, \\
sta per tipo-astrazione (grande lambda), così come l'astrazione del dizionario. Inoltre, @
sta per tipo/applicazione dizionario.
Prima di farlo, richiamare il tipo di (.)
:
(.) :: forall a b c . (b -> a) -> (c -> b) -> (c -> a)
Ecco il codice annotato (attenzione, non per i deboli di cuore):
fComp :: F b c -> F a b -> F a c
fComp (f :: forall f1. Functor f1 => f1 b -> f1 c)
(f':: forall f2. Functor f2 => f2 a -> f2 b)
= \\ ff :: (* -> *) ->
\\ ffD :: Functor ff ->
((.) @ (ff c) @ (ff b) @ (ff a)) -- instantiated composition
(f @ ff @ ffD) -- first argument of (.)
(f' @ ff @ ffD) -- second argument of (.)
(Sopra ho fatto finta a
, b
, c
sono costanti di tipo per evitare ulteriori lambda di tipo di livello.)
Le parti importanti:
f
e f'
vengono utilizzati a tipi specifici. Vale a dire che vengono applicati agli argomenti a livello di codice prima di essere inviati a (.)
.
(.)
viene applicata a livello di tipo di tipi (ff c
, ecc) che non sono i politipi di f
e f'
Come potete vedere, il codice originale è ben lungi dall'essere banale. L'inferenza di tipo è in grado di aggiungere i lambda e le applicazioni necessari a livello di tipo. Dopo che questi sono stati aggiunti, non possiamo più semplicemente stipulare il contratto fComp
.
Nella variante senza punti, l'inferenza di tipo avrebbe dovuto fare di più rispetto al caso. Mentre il primo argomento di fComp
è di tipo F a b
, il primo argomento di (.)
deve essere del formato x -> y
, che non unifica per F a b = forall g . ...
. In effetti, non c'è modo per risolvere con successo il tentativo di digitazione di seguito:
fComp :: F b c -> F a b -> F a c
fComp
= \\ ff :: (* -> *) ->
\\ ffD :: Functor ff ->
((.) @ ???a @ ???b @ ???c)
Sopra non c'è ???a
, ... che possono portare al tipo desiderato.
L'unica possibilità sarebbe quella di istanziare le forall
variabili -quantified nascosti nelle F x y
tipi, ma per farlo abbiamo bisogno di un punto di . Il compilatore potrebbe eta-espandere quel codice per te in modo che i punti appaiano e così possano essere istanziati, teoricamente parlando, ma in pratica no.
(Inoltre, l'espansione eta non è sempre valida in Haskell: ad esempio seq (undefined::()->()) 3
loop mentre seq (\x->(undefined::()->()) x) 3
restituisce 3
).
Quali libri devo leggere per comprendere appieno questo? –
@ErikAllik: Uh, non ne sono sicuro. Vado a prendere roba mentre cammino. Puoi dare un'occhiata al documento [Typing Haskell in Haskell] (http://web.cecs.pdx.edu/~mpj/thih/thih.pdf) e potrebbe anche essere trattato in * Tipi e linguaggi di programmazione * (TAPL) insieme a una tonnellata di altro materiale. –