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 Monadcapturando 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 vacuousfoi 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 avalores 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, Voidraramente é 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 absurdcomo a operação de propósito geral Void.
Para outro exemplo, imagine usar Either e vpara capturar cálculos que, felizmente, fornecem um, vmas 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 ecomo 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 Voidcompactar 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 Differentiablee Traversablefunctor f. Usamos Term f Voidpara 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.
absurdfunção foi usada neste artigo lidando com aContmônada: haskellforall.com/2012/12/the-continuation-monad.html