A absurd
função in Data.Void
tem a seguinte assinatura, onde Void
é o tipo logicamente inabitado exportado por esse pacote:
-- | Since 'Void' values logically don't exist, this witnesses the logical
-- reasoning tool of \"ex falso quodlibet\".
absurd :: Void -> a
Eu conheço lógica suficiente para obter a observação da documentação de que isso corresponde, pela correspondência proposições como tipos, à fórmula válida ⊥ → a
.
O que estou intrigado e curioso é: em que tipo de problemas práticos de programação essa função é útil? Estou pensando que talvez seja útil em alguns casos como uma forma segura de lidar exaustivamente com casos "não podem acontecer", mas não sei o suficiente sobre os usos práticos de Curry-Howard para dizer se essa ideia está no caminho certo em tudo.
EDITAR: Exemplos de preferência em Haskell, mas se alguém quiser usar uma linguagem de digitação dependente, não vou reclamar ...
fonte
absurd
função foi usada neste artigo lidando com aCont
mônada: haskellforall.com/2012/12/the-continuation-monad.htmlabsurd
como uma direção do isomorfismo entreVoid
eforall a. a
.Respostas:
A vida é um pouco difícil, já que Haskell não é rigoroso. O caso de uso geral é lidar com caminhos impossíveis. Por exemplo
Isso acabou sendo um tanto útil. Considere um tipo simples para
Pipes
esta é uma versão estrita e simplificada do tipo de tubos padrão da
Pipes
biblioteca de Gabriel Gonzales . Agora, podemos codificar um tubo que nunca cede (ou seja, um consumidor) comoisso realmente nunca cede. A implicação disso é que a regra de dobra adequada para a
Consumer
éou alternativamente, que você pode ignorar o caso de rendimento ao lidar com os consumidores. Esta é a versão geral deste padrão de projeto: use tipos de dados polimórficos e
Void
se livre das possibilidades quando precisar.Provavelmente, o uso mais clássico de
Void
é no CPS.ou seja, a
Continuation
é uma função que nunca retorna.Continuation
é a versão do tipo de "não". A partir disso, obtemos uma mônada de CPS (correspondente à lógica clássica)como Haskell é puro, não podemos obter nada desse tipo.
fonte
Void
é desabitado. Em Haskell, ele contém_|_
. Em uma linguagem estrita, um construtor de dados que recebe um argumento do tipoVoid
nunca pode ser aplicado, portanto, o lado direito da correspondência de padrão é inacessível. Em Haskell, você precisa usar um!
para garantir isso, e o GHC provavelmente não perceberá que o caminho está inacessível._|_
? e sofre da mesma limitação então?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.)
Você certamente pode tornar isso um
Functor
, capturando a noção de renomeação eMonad
capturando a noção de substituição.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?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 quevacuous
foi adicionado aData.Void
...Ou escreva um avaliador. Aqui estão os valores com variáveis livres em
b
.Acabei de representar lambdas como encerramentos. O avaliador é parametrizado por um ambiente que mapeia variáveis livres em
a
valoresb
.Você adivinhou. Para avaliar um termo fechado em qualquer alvo
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ê passaabsurd
como a operação de propósito geralVoid
.Para outro exemplo, imagine usar
Either e v
para capturar cálculos que, felizmente, fornecem um,v
mas podem gerar uma exceção de tipoe
. Você pode usar essa abordagem para documentar o risco de mau comportamento de maneira uniforme. Para cálculos perfeitamente bem comportados nesta configuração, tomee
como eVoid
, em seguida, usepara correr com segurança ou
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.
Decidi não excluir o resto, embora não seja exatamente relevante.
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 livresem qualquer sintaxe gerada livremente a partir de um
Differentiable
eTraversable
functorf
. UsamosTerm 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. Eabsurd
é assim que você o usa.fonte
forall f. vacuous f = unsafeCoerce f
uma regra de reescrita válida do GHC?Functor
casos poderia ser GADTs que não são realmente qualquer coisa como functors.Functor
não infringiria afmap id = id
regra? Ou é isso que você quer dizer com "falso" aqui?Isso é precisamente correto.
Você poderia dizer que
absurd
não é mais útil do queconst (error "Impossible")
. No entanto, ele é restrito ao tipo, de modo que sua única entrada pode ser algo do tipoVoid
, um tipo de dados que é intencionalmente deixado inabitado. Isso significa que não há nenhum valor real para o qual você possa passarabsurd
. Se você alguma vez acabar em um ramo de código onde o verificador de tipo pensa que você tem acesso a algo do tipoVoid
, então, bem, você está em uma situação absurda . Então, você apenas usaabsurd
para basicamente marcar que esse ramo do código nunca deve ser alcançado."Ex falso quodlibet" significa literalmente "de [uma] falsa [proposição], segue-se qualquer coisa". Portanto, quando você descobrir que está segurando um dado cujo tipo é
Void
, você sabe que tem evidências falsas em suas mãos. Você pode, portanto, preencher qualquer lacuna que quiser (viaabsurd
), porque de uma proposição falsa, tudo se segue.Eu escrevi uma postagem no blog sobre as ideias por trás do Conduit que tem um exemplo de uso
absurd
.http://unknownparallel.wordpress.com/2012/07/30/pipes-to-conduits-part-6-leftovers/#running-a-pipeline
fonte
Geralmente, você pode usá-lo para evitar correspondências de padrões aparentemente parciais. Por exemplo, pegando uma aproximação das declarações de tipo de dados desta resposta :
Então você poderia usar
absurd
assim, por exemplo:fonte
Existem diferentes maneiras de representar o tipo de dados vazio . Um é um tipo de dados algébrico vazio. Outra maneira é torná-lo um alias para ∀α.α ou
em Haskell - é assim que podemos codificá-lo no Sistema F (consulte o Capítulo 11 de Provas e tipos ). Essas duas descrições são obviamente isomórficas e o isomorfismo é testemunhado por
\x -> x :: (forall a.a) -> Void
e porabsurd :: Void -> a
.Em alguns casos, preferimos a variante explícita, geralmente se o tipo de dados vazio aparece em um argumento de uma função, ou em um tipo de dados mais complexo, como em Data.Conduit :
Em alguns casos, preferimos a variante polimórfica, geralmente o tipo de dados vazio está envolvido no tipo de retorno de uma função.
absurd
surge quando estamos convertendo entre essas duas representações.Por exemplo,
callcc :: ((a -> m b) -> m a) -> m a
usa (implícito)forall b
. Também poderia ser do tipo((a -> m Void) -> m a) -> m a
, porque uma chamada para a continação não retorna realmente, ela transfere o controle para outro ponto. Se quiséssemos trabalhar com continuações, poderíamos definir(Poderíamos usar,
type Continuation' r a = forall b . a -> Cont r b
mas isso exigiria tipos de classificação 2.) E então,vacuousM
converte issoCont r Void
emCont r b
.(Observe também que você pode usar haskellers.com para pesquisar o uso (dependências reversas) de um determinado pacote, como para ver quem e como usa o pacote void .)
fonte
TypeApplications
pode ser usado para ser mais explícito sobre detalhes deproof :: (forall a. a) -> Void
:proof fls = fls @Void
.Em linguagens com tipos dependentes como Idris, é provavelmente mais útil do que em Haskell. Normalmente, em uma função total, quando você padroniza a correspondência de um valor que realmente não pode ser empurrado para a função, você construiria um valor do tipo não habitado e utilizaria
absurd
para finalizar a definição de caso.Por exemplo, esta função remove um elemento de uma lista com a restrição de nível de tipo que está presente lá:
Onde o segundo caso é dizer que existe um certo elemento em uma lista vazia, o que é, bem absurdo. Em geral, entretanto, o compilador não sabe disso e muitas vezes temos que ser explícitos. Então, o compilador pode verificar se a definição da função não é parcial e obter garantias de tempo de compilação mais fortes.
Do ponto de vista de Curry-Howard, onde estão as proposições, então
absurd
é uma espécie de QED em uma prova por contradição.fonte