Considere esta representación para términos lambda parametrizados por sus variables libres. (Véanse los artículos de Bellegarde y Hook 1994, Bird y Paterson 1999, Altenkirch y Reus 1999.)
data Tm a = Var a
| Tm a :$ Tm a
| Lam (Tm (Maybe a))
Ciertamente puede hacer que esto Functor
capture la noción de cambio de nombre y Monad
capture la noción de sustitución.
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))
Ahora considere los términos cerrados : estos son los habitantes de Tm Void
. Debería poder incrustar los términos cerrados en términos con variables libres arbitrarias. ¿Cómo?
fmap absurd :: Tm Void -> Tm a
El problema, por supuesto, es que esta función atravesará el término sin hacer nada precisamente. Pero es un toque más honesto que unsafeCoerce
. Y es por eso que vacuous
se agregó a Data.Void
...
O escribe un evaluador. Aquí hay valores con variables libres en formato b
.
data Val b
= b :$$ [Val b] -- a stuck application
| forall a. LV (a -> Val b) (Tm (Maybe a)) -- we have an incomplete environment
Acabo de representar lambdas como cierres. El evaluador está parametrizado por un entorno que asigna variables libres a
a valores superiores 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
Lo adivinaste. Para evaluar un plazo cerrado en cualquier objetivo
eval absurd :: Tm Void -> Val b
De manera más general, Void
rara vez se usa por sí solo, pero es útil cuando desea instanciar un parámetro de tipo de una manera que indique algún tipo de imposibilidad (por ejemplo, aquí, usando una variable libre en un término cerrado). A menudo, estos tipos parametrizadas vienen con funciones de orden superior operaciones de elevación de los parámetros a las operaciones en todo el tipo (por ejemplo, aquí, fmap
, >>=
, eval
). Así que pasa absurd
como la operación de propósito general Void
.
Para otro ejemplo, imagínese usar Either e v
para capturar cálculos que con suerte le den un v
pero podrían generar una excepción de tipo e
. Puede utilizar este enfoque para documentar el riesgo de mal comportamiento de manera uniforme. Para cálculos que se comportan perfectamente en esta configuración, tómatelo e
y Void
luego usa
either absurd id :: Either Void v -> v
para correr con seguridad o
either absurd Right :: Either Void v -> Either e v
para integrar componentes seguros en un mundo inseguro.
Ah, y un último hurra, manejar un "no puede suceder". Aparece en la construcción de cremallera genérica, en todos los lugares donde el cursor no puede estar.
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!
Decidí no borrar el resto, aunque no es exactamente relevante.
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)
De hecho, tal vez sea relevante. Si se siente aventurero, este artículo inacabado muestra cómo utilizar Void
para comprimir la representación de términos con variables libres
data Term f x = Var x | Con (f (Term f x)) -- the Free monad, yet again
en cualquier sintaxis generada libremente desde a Differentiable
y Traversable
functor f
. Usamos Term f Void
para representar regiones sin variables libres y [D f (Term f Void)]
para representar tubos que hacen un túnel a través de regiones sin variables libres, ya sea a una variable libre aislada o a una unión en las rutas a dos o más variables libres. Debo terminar ese artículo en algún momento.
Para un tipo sin valores (o al menos, ninguno de los que valga la pena hablar en compañía cortés), Void
es muy útil. Y así absurd
es como lo usas.
absurd
función se ha utilizado en este artículo sobre laCont
mónada: haskellforall.com/2012/12/the-continuation-monad.html