(NB questo combina un po 'da entrambi mio e @ commenti di Gabriel sopra.)
E' possibile per ogni abitante del punto Fix
ed di un Functor
essere infinita, cioè let x = (Fix (Id x)) in x === (Fix (Id (Fix (Id ...))))
è l'solo abitante Fix Identity
. Free
differisce immediatamente da Fix
in quanto garantisce almeno finito abitante di Free f
. Infatti, se Fix f
ha abitanti infiniti allora lo Free f
ha infiniti abitanti finiti.
Un altro effetto collaterale immediato di questa sconfinatezza è che Functor f => Fix f
non è più un Functor
. Avremmo bisogno di implementare fmap :: Functor f => (a -> b) -> (f a -> f b)
, ma Fix
ha "riempito tutti i buchi" in f a
che conteneva il a
, quindi non abbiamo più alcun a
s per applicare la nostra funzione fmap
a.
Questo è importante per la creazione di Monad
s perché vorremmo implementare return :: a -> Free f a
e hanno, per esempio, questa legge tenere fmap f . return = return . f
, ma non ha nemmeno senso in un Functor f => Fix f
.
Quindi, come fa Free
"correggere" questi punti deboli Fix
punto? "Aumenta" il nostro functor di base con il costruttore Pure
. Pertanto, per tutti gli Functor f
, Pure :: a -> Free f a
. Questo è il nostro abitante del tipo garantito-to-be-finito. Inoltre, ci dà immediatamente una definizione ben fatta di return
.
return = Pure
Così si potrebbe pensare a questa aggiunta come tirando fuori "albero" potenzialmente infinito di annidate Functor
s create da Fix
e mescolando in un certo numero di "viventi" gemme, rappresentati da Pure
. Creiamo nuovi boccioli usando return
che potrebbe essere interpretato come una promessa di "tornare" al bocciolo in seguito e aggiungere più calcoli. In effetti, questo è esattamente ciò che fa flip (>>=) :: (a -> Free f b) -> (Free f a -> Free f b)
. Data una funzione di "continuazione" f :: a -> Free f b
che può essere applicata ai tipi a
, riconsegniamo il nostro albero restituendo a ogni Pure a
e sostituendolo con la continuazione calcolata come f a
. Questo ci permette di "crescere" il nostro albero.
Ora, Free
è chiaramente più generale di Fix
. Per guidare questa casa, è possibile vedere qualsiasi tipo Functor f => Fix f
come sottotipo del corrispondente Free f a
! Basta scegliere dove abbiamo data Void = Void Void
(cioè, un tipo che non può essere costruito, è il tipo vuoto, non ha istanze).
Per renderlo più chiaro, siamo in grado di rompere i nostri Fix
'D Functor
s con break :: Fix f -> Free f a
e quindi provare a invertire con affix :: Free f Void -> Fix f
.
break (Fix f) = Free (fmap break f)
affix (Free f) = Fix (fmap affix f)
Nota prima che affix
non ha bisogno di gestire il caso Pure x
perché in questo caso x :: Void
e, quindi, non può davvero essere lì, così Pure x
è assurdo e ci limiteremo a ignorarlo.
Si noti inoltre che tipo di ritorno break
s' è un po' sottile, in quanto il tipo a
appare solo nel tipo di ritorno, Free f a
, in modo tale che è completamente inaccessibile a qualsiasi utente di break
. "Completamente inaccessibile" e "non può essere istanziato" ci danno il primo suggerimento che, nonostante i tipi, affix
e break
sono inversi, ma possiamo solo dimostrarlo.
(break . affix) (Free f)
=== [definition of affix]
break (Fix (fmap affix f))
=== [definition of break]
Free (fmap break (fmap affix f))
=== [definition of (.)]
Free ((fmap break . fmap affix) f)
=== [functor coherence laws]
Free (fmap (break . affix) f)
che dovrebbe mostrare (co-induttivo, o semplicemente intuitivamente, forse) che (break . affix)
è un'identità. L'altra direzione passa in modo completamente identico.
Quindi, si spera che questo mostri che Free f
è più grande di Fix f
per tutti Functor f
.
Quindi perché usare mai Fix
? Bene, a volte si desidera solo le proprietà di Free f Void
a causa di alcuni effetti collaterali della stratificazione di f
s. In questo caso, chiamandolo Fix f
è più chiaro che non dovremmo provare a (>>=)
o fmap
per il tipo. Inoltre, poiché Fix
è solo un newtype
potrebbe essere più facile per il compilatore "compilare" i livelli di Fix
in quanto svolge comunque solo un ruolo semantico.
- Nota: possiamo più formalmente parlare di come
Void
e forall a. a
sono tipi isomorfi al fine di vedere più chiaramente come i tipi di affix
e break
sono armoniosi. Ad esempio, abbiamo absurd :: Void -> a
come absurd (Void v) = absurd v
e unabsurd :: (forall a. a) -> Void
come unabsurd a = a
. Ma questi diventano un po 'sciocchi.
Il vantaggio principale dell'istanza Monade è 'do' notazione e riutilizzo dei combinatori di' Control.Monad' (come '' replicateM_' e forM_' negli esempi). Un trucco comune è costruire il tipo usando una monade libera, ma poi richiedere che il risultato abbia tipo "FreeT f m Void" in modo che possa essere convertito nel punto fisso di un functor. –
L'istanza 'Monad' arricchisce' Fix' con due cose - terminazione definita da 'return' e naturale" estensione "da' (>> =) '. Un normale '(Fix f)' non può garantire che abbia alcun valore (finito) (cioè '(Fix Identity)'), ma '(Free f)' è sempre abitato almeno da 'Pure'. –
@GabrielGonzalez puoi approfondire la seconda parte del tuo commento? Quale sarebbe un caso d'uso di esempio? –