qual è il tipo di x nell'ultimo definizione?
Prima di dire altro sull'argomento: puoi chiedere a GHC! GHC 7.8 e versioni successive supporta TypedHoles
, il che significa che se si inserisce un carattere di sottolineatura in un'espressione (non un pattern) e si preme il comando load o compile, si ottiene un messaggio con il tipo previsto del carattere di sottolineatura e i tipi delle variabili nell'ambito locale.
newtype Compose f g a = Compose { getCompose :: f (g a) }
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = _
GHC dice ora, con alcune parti omesse:
Notes.hs:6:26: Found hole ‘_’ with type: Compose f g b …
-- omitted type variable bindings --
Relevant bindings include
x :: f (g a)
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:21)
f :: a -> b
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:10)
fmap :: (a -> b) -> Compose f g a -> Compose f g b
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:5)
In the expression: _
In an equation for ‘fmap’: fmap f (Compose x) = _
In the instance declaration for ‘Functor (Compose f g)’
Ci si va, x :: f (g a)
. E dopo un po 'di pratica, TypedHoles
può aiutarti enormemente a capire il codice polimorfico complesso. Proviamo a capire il nostro codice attuale scrivendo il lato destro da zero.
Abbiamo già visto che il foro aveva il tipo Compose f g b
. Pertanto dobbiamo avere una Compose _
sul lato destro:
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose _
Il nuovo buco è di tipo f (g b)
.Ora dovremmo guardiamo al contesto:
Relevant bindings include
x :: f (g a)
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:21)
f :: a -> b
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:10)
fmap :: (a -> b) -> Compose f g a -> Compose f g b
(bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:5)
L'obiettivo è quello di ottenere un f (g b)
con gli ingredienti nel contesto. Il numero fmap
nella lista sopra purtroppo si riferisce alla funzione in fase di definizione, che a volte è utile, ma non qui. Stiamo meglio sapendo che f
e g
sono entrambi i funtori, quindi possono essere sostituiti da fmap
. Dal momento che abbiamo x :: f (g a)
, abbiamo immaginare che dovremmo fmap
qualcosa sopra x
per ottenere un f (g b)
:
fmap f (Compose x) = Compose (fmap _ x)
Ora il buco diventa g a -> g b
. Ma g a -> g b
è molto semplice ora, dato che abbiamo f :: a -> b
e g
è un Functor
, quindi abbiamo anche fmap :: (a -> b) -> g a -> g b
e quindi fmap f :: g a -> g b
.
fmap f (Compose x) = Compose (fmap (fmap f) x)
E abbiamo finito.
per concludere la tecnica:
Inizia con mettere un buco nel luogo in cui non si sa come procedere. Qui abbiamo iniziato col mettere il buco al posto dell'intero lato destro, ma spesso avrete una buona idea della maggior parte delle parti di un'implementazione e avrete bisogno del buco in una subexpression problematica specifica.
Osservando i tipi, provare a restringere quali implementazioni potrebbero portare all'obiettivo e quali no. Inserisci una nuova espressione e riposiziona il buco. In gergo dell'assistente tecnico questo è chiamato "raffinamento".
Ripeti il passaggio 2 finché non ottieni l'obiettivo, nel qual caso hai finito, oppure l'obiettivo attuale sembra impossibile, nel qual caso fai il backtrack fino all'ultima scelta non ovvia che hai fatto e prova un raffinamento alternativo.
La tecnica di cui sopra viene talvolta chiamata scherzosamente "tipo tetris". Un possibile inconveniente è che è possibile implementare codice complesso semplicemente suonando il "tetris", senza effettivamente capire cosa si sta facendo. E a volte, dopo essere andato troppo lontano, si rimane seriamente bloccati nel gioco, e quindi bisogna iniziare a pensare veramente al problema. Ma alla fine ti consente di capire codice che altrimenti sarebbe molto difficile da capire.
Io personalmente uso TypedHoles
tutto il tempo e fondamentalmente come una reflex. Sono arrivato a contare così tanto su di esso in modo che in un'occasione in cui dovevo tornare a GHC 7.6 mi sentivo piuttosto a disagio (ma fortunatamente si è can emulate holes anche lì).
Ecco quello che sto scrivendo a: * principale> lascia x1 = [Nodo 1 Vuoto] * principale> lascia x2 = x1 FMAP (succ FMAP) ERRORE! Cosa sto facendo male? Ora provo con il Maybe .... –
Non riesco ancora a farlo funzionare: * Main> let y1 = Node (Just 1) Empty Empty * Main> let y2 = y1 fmap (fmap succ) ERROR! –
Ecco perché ho chiesto esplicitamente un "esempio di lavoro chiaro e completo". Grazie! –