Considera questa rappresentazione per termini lambda parametrizzati dalle loro variabili libere. (Vedi articoli di Bellegarde e Hook 1994, Bird e Paterson 1999, Altenkirch e Reus 1999.)
data Tm a = Var a
| Tm a :$ Tm a
| Lam (Tm (Maybe a))
Puoi certamente renderlo un Functor
, catturare il concetto di rinomina e Monad
catturare il concetto di sostituzione.
instance Functor Tm where
fmap rho (Var a) = Var (rho a)
fmap rho (f :$ s) = fmap rho f :$ fmap rho s
fmap rho (Lam t) = Lam (fmap (fmap rho) t)
instance Monad Tm where
return = Var
Var a >>= sig = sig a
(f :$ s) >>= sig = (f >>= sig) :$ (s >>= sig)
Lam t >>= sig = Lam (t >>= maybe (Var Nothing) (fmap Just . sig))
Consideriamo ora i termini chiusi : questi sono gli abitanti di Tm Void
. Dovresti essere in grado di incorporare i termini chiusi in termini con variabili libere arbitrarie. Come?
fmap absurd :: Tm Void -> Tm a
Il problema, ovviamente, è che questa funzione attraverserà il termine senza fare proprio nulla. Ma è un tocco più onesto di unsafeCoerce
. Ed è per questo che è vacuous
stato aggiunto a Data.Void
...
Oppure scrivi un valutatore. Ecco i valori con variabili libere in b
.
data Val b
= b :$$ [Val b] -- a stuck application
| forall a. LV (a -> Val b) (Tm (Maybe a)) -- we have an incomplete environment
Ho appena rappresentato i lambda come chiusure. Il valutatore è parametrizzato da un ambiente che associa le variabili libere a
ai valori superiori b
.
eval :: (a -> Val b) -> Tm a -> Val b
eval g (Var a) = g a
eval g (f :$ s) = eval g f $$ eval g s where
(b :$$ vs) $$ v = b :$$ (vs ++ [v]) -- stuck application gets longer
LV g t $$ v = eval (maybe v g) t -- an applied lambda gets unstuck
eval g (Lam t) = LV g t
Hai indovinato. Per valutare un termine chiuso a qualsiasi target
eval absurd :: Tm Void -> Val b
Più in generale, Void
è usato raramente da solo, ma è utile quando si desidera istanziare un parametro di tipo in un modo che indica una sorta di impossibilità (ad esempio, qui, utilizzando una variabile libera in un termine chiuso). Spesso questi tipi parametrizzati vengono con funzioni di ordine superiore operazioni di sollevamento sui parametri per operazioni del tipo complesso (ad esempio, qui, fmap
, >>=
, eval
). Quindi passi absurd
come operazione generica Void
.
Per un altro esempio, immagina di usare Either e v
per acquisire calcoli che si spera ti diano un v
ma potrebbero sollevare un'eccezione di tipo e
. È possibile utilizzare questo approccio per documentare in modo uniforme il rischio di comportamenti scorretti. Per calcoli perfettamente funzionanti in questa impostazione, prendi e
per essere Void
, quindi usa
either absurd id :: Either Void v -> v
per correre in sicurezza o
either absurd Right :: Either Void v -> Either e v
per incorporare componenti sicuri in un mondo non sicuro.
Oh, e un ultimo evviva, gestire un "non può succedere". Si presenta nella costruzione generica della cerniera, ovunque il cursore non possa essere.
class Differentiable f where
type D f :: * -> * -- an f with a hole
plug :: (D f x, x) -> f x -- plugging a child in the hole
newtype K a x = K a -- no children, just a label
newtype I x = I x -- one child
data (f :+: g) x = L (f x) -- choice
| R (g x)
data (f :*: g) x = f x :&: g x -- pairing
instance Differentiable (K a) where
type D (K a) = K Void -- no children, so no way to make a hole
plug (K v, x) = absurd v -- can't reinvent the label, so deny the hole!
Ho deciso di non cancellare il resto, anche se non è esattamente rilevante.
instance Differentiable I where
type D I = K ()
plug (K (), x) = I x
instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
type D (f :+: g) = D f :+: D g
plug (L df, x) = L (plug (df, x))
plug (R dg, x) = R (plug (dg, x))
instance (Differentiable f, Differentiable g) => Differentiable (f :*: g) where
type D (f :*: g) = (D f :*: g) :+: (f :*: D g)
plug (L (df :&: g), x) = plug (df, x) :&: g
plug (R (f :&: dg), x) = f :&: plug (dg, x)
In realtà, forse è rilevante. Se ti senti avventuroso, questo articolo incompleto mostra come utilizzare Void
per comprimere la rappresentazione dei termini con variabili libere
data Term f x = Var x | Con (f (Term f x)) -- the Free monad, yet again
in qualsiasi sintassi generata liberamente da a Differentiable
e Traversable
funtore f
. Usiamo Term f Void
per rappresentare regioni senza variabili libere e [D f (Term f Void)]
per rappresentare tubi che scorrono attraverso regioni senza variabili libere o su una variabile libera isolata o su una giunzione nei percorsi di due o più variabili libere. Devo finire quell'articolo prima o poi.
Per un tipo senza valori (o almeno, nessuno di cui valga la pena parlare in gentile compagnia), Void
è straordinariamente utile. Ed absurd
è come lo usi.
absurd
funzione è stata utilizzata in questo articolo che tratta dellaCont
monade: haskellforall.com/2012/12/the-continuation-monad.html