Considérez cette représentation pour les termes lambda paramétrés par leurs variables libres. (Voir les articles de Bellegarde et Hook 1994, Bird et Paterson 1999, Altenkirch et Reus 1999.)
data Tm a = Var a
| Tm a :$ Tm a
| Lam (Tm (Maybe a))
Vous pouvez certainement en faire un Functor
, capturer la notion de changement de nom et Monad
capturer la notion de substitution.
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))
Considérons maintenant les termes fermés : ce sont les habitants de Tm Void
. Vous devriez pouvoir intégrer les termes fermés dans des termes avec des variables libres arbitraires. Comment?
fmap absurd :: Tm Void -> Tm a
Le hic, bien sûr, est que cette fonction traversera le terme sans rien faire. Mais c'est une touche plus honnête que unsafeCoerce
. Et c'est pourquoi a vacuous
été ajouté à Data.Void
...
Ou écrivez un évaluateur. Voici les valeurs avec des variables libres dans b
.
data Val b
= b :$$ [Val b] -- a stuck application
| forall a. LV (a -> Val b) (Tm (Maybe a)) -- we have an incomplete environment
Je viens de représenter les lambdas comme des fermetures. L'évaluateur est paramétré par un environnement mappant les variables libres a
vers les valeurs over 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
Tu l'as deviné. Pour évaluer un terme fermé à n'importe quelle cible
eval absurd :: Tm Void -> Val b
Plus généralement, Void
est rarement utilisé seul, mais est pratique lorsque vous voulez instancier un paramètre de type d'une manière qui indique une sorte d'impossibilité (par exemple, ici, en utilisant une variable libre dans un terme fermé). Souvent , ces types paramétrés viennent avec des fonctions d'ordre supérieur des opérations de levage sur les paramètres des opérations du type entier (par exemple, ici, fmap
, >>=
, eval
). Vous passez donc absurd
comme opération à usage général Void
.
Pour un autre exemple, imaginez utiliser Either e v
pour capturer des calculs qui, espérons-le, vous donneront un v
mais pourraient soulever une exception de type e
. Vous pouvez utiliser cette approche pour documenter uniformément le risque de mauvais comportement. Pour des calculs parfaitement bien comportés dans ce cadre, prenez e
pour être Void
, puis utilisez
either absurd id :: Either Void v -> v
pour courir en toute sécurité ou
either absurd Right :: Either Void v -> Either e v
pour intégrer des composants sûrs dans un monde dangereux.
Oh, et un dernier hourra, gérer un "impossible". Il apparaît dans la construction générique de la fermeture à glissière, partout où le curseur ne peut pas être.
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!
J'ai décidé de ne pas supprimer le reste, même si ce n'est pas tout à fait pertinent.
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)
En fait, c'est peut-être pertinent. Si vous vous sentez aventureux, cet article inachevé montre comment utiliser Void
pour compresser la représentation des termes avec des variables libres
data Term f x = Var x | Con (f (Term f x)) -- the Free monad, yet again
dans n'importe quelle syntaxe générée librement à partir d'un foncteur Differentiable
et . Nous utilisons pour représenter des régions sans variables libres et pour représenter des tubes qui traversent des régions sans variables libres, soit vers une variable libre isolée, soit vers une jonction dans les chemins vers deux ou plusieurs variables libres. Doit finir cet article un jour.Traversable
f
Term f Void
[D f (Term f Void)]
Pour un type sans valeurs (ou du moins, aucune digne de parler en compagnie polie), Void
est remarquablement utile. Et absurd
c'est comment vous l'utilisez.
absurd
fonction a été utilisée dans cet article traitant de laCont
monade: haskellforall.com/2012/12/the-continuation-monad.html