Considere esta representação para termos lambda parametrizados por suas variáveis livres. (Ver artigos de 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))
Você certamente pode tornar isso um Functor
, capturando a noção de renomeação e Monad
capturando a noção de substituição.
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))
Agora considere os termos fechados : estes são os habitantes de Tm Void
. Você deve ser capaz de incorporar os termos fechados em termos com variáveis livres arbitrárias. Quão?
fmap absurd :: Tm Void -> Tm a
O problema, é claro, é que essa função atravessará o termo sem fazer exatamente nada. Mas é um pouco mais honesto do que unsafeCoerce
. E é por isso que vacuous
foi adicionado a Data.Void
...
Ou escreva um avaliador. Aqui estão os valores com variáveis livres em b
.
data Val b
= b :$$ [Val b] -- a stuck application
| forall a. LV (a -> Val b) (Tm (Maybe a)) -- we have an incomplete environment
Acabei de representar lambdas como encerramentos. O avaliador é parametrizado por um ambiente que mapeia variáveis livres em a
valores 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
Você adivinhou. Para avaliar um termo fechado em qualquer alvo
eval absurd :: Tm Void -> Val b
De modo mais geral, Void
raramente é usado sozinho, mas é útil quando você deseja instanciar um parâmetro de tipo de uma forma que indica algum tipo de impossibilidade (por exemplo, aqui, usando uma variável livre em um termo fechado). Muitas vezes, estes tipos parametrizados vêm com funções de ordem superior operações de elevação sobre os parâmetros para as operações em todo o tipo (por exemplo, aqui, fmap
, >>=
, eval
). Então você passa absurd
como a operação de propósito geral Void
.
Para outro exemplo, imagine usar Either e v
para capturar cálculos que, felizmente, fornecem um, v
mas podem gerar uma exceção de tipo e
. Você pode usar essa abordagem para documentar o risco de mau comportamento de maneira uniforme. Para cálculos perfeitamente bem comportados nesta configuração, tome e
como e Void
, em seguida, use
either absurd id :: Either Void v -> v
para correr com segurança ou
either absurd Right :: Either Void v -> Either e v
para incorporar componentes seguros em um mundo inseguro.
Ah, e um último viva, lidar com um "não pode acontecer". Ele aparece na construção genérica do zíper, em todos os lugares onde o cursor não pode 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!
Decidi não excluir o resto, embora não seja exatamente 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)
Na verdade, talvez seja relevante. Se você está se sentindo aventureiro, este artigo inacabado mostra como Void
compactar a representação de termos com variáveis livres
data Term f x = Var x | Con (f (Term f x)) -- the Free monad, yet again
em qualquer sintaxe gerada livremente a partir de um Differentiable
e Traversable
functor f
. Usamos Term f Void
para representar regiões sem variáveis livres e [D f (Term f Void)]
para representar tubos tunelando através de regiões sem variáveis livres para uma variável livre isolada, ou para uma junção nos caminhos para duas ou mais variáveis livres. Devo terminar esse artigo algum dia.
Para um tipo sem valores (ou, pelo menos, nenhum que valha a pena falar em companhia educada), Void
é extremamente útil. E absurd
é assim que você o usa.
absurd
função foi usada neste artigo lidando com aCont
mônada: haskellforall.com/2012/12/the-continuation-monad.html