Eu descobri o isomorfismo de Curry-Howard relativamente tarde em minha vida de programação e talvez isso contribua para que eu fique totalmente fascinado por ele. Isso implica que para cada conceito de programação existe um análogo preciso na lógica formal e vice-versa. Aqui está uma lista "básica" dessas analogias, tirada do topo da minha cabeça:
program/definition | proof
type/declaration | proposition
inhabited type | theorem/lemma
function | implication
function argument | hypothesis/antecedent
function result | conclusion/consequent
function application | modus ponens
recursion | induction
identity function | tautology
non-terminating function | absurdity/contradiction
tuple | conjunction (and)
disjoint union | disjunction (or) -- corrected by Antal S-Z
parametric polymorphism | universal quantification
Então, à minha pergunta: quais são algumas das implicações mais interessantes / obscuras desse isomorfismo? Não sou um lógico, então tenho certeza de que apenas arranhei a superfície com esta lista.
Por exemplo, aqui estão algumas noções de programação para as quais não tenho conhecimento de nomes expressivos na lógica:
currying | "((a & b) => c) iff (a => (b => c))"
scope | "known theory + hypotheses"
E aqui estão alguns conceitos lógicos que ainda não defini em termos de programação:
primitive type? | axiom
set of valid programs? | theory
Editar:
Aqui estão mais algumas equivalências coletadas das respostas:
function composition | syllogism -- from Apocalisp
continuation-passing | double negation -- from camccann
fonte
goto | jumping to conclusions
Respostas:
Já que você pediu explicitamente pelos mais interessantes e obscuros:
Você pode estender o CH a muitas lógicas e formulações lógicas interessantes para obter uma variedade realmente ampla de correspondências. Aqui, tentei me concentrar em alguns dos mais interessantes em vez de nos obscuros, além de alguns outros fundamentais que ainda não surgiram.
EDIT: Uma referência que eu recomendo para qualquer pessoa interessada em aprender mais sobre extensões de CH:
"A Judgmental Reconstruction of Modal Logic" http://www.cs.cmu.edu/~fp/papers/mscs00.pdf - este é um ótimo lugar para começar porque começa a partir dos primeiros princípios e muitos deles se destinam a ser acessível a não lógicos / teóricos da linguagem. (Eu sou o segundo autor, então sou tendencioso.)
fonte
Você está confundindo um pouco as coisas em relação à não terminação. A falsidade é representada por tipos desabitados , que por definição não podem ser não terminados porque não há nada desse tipo para avaliar em primeiro lugar.
A não terminação representa contradição - uma lógica inconsistente. Uma lógica inconsistente, é claro, permitirá que você prove qualquer coisa , incluindo a falsidade, entretanto.
Ignorando as inconsistências, os sistemas de tipos normalmente correspondem a uma lógica intuicionista e são necessariamente construtivistas , o que significa que certas peças da lógica clássica não podem ser expressas diretamente, se o forem. Por outro lado, isso é útil, porque se um tipo é uma prova construtiva válida, então um termo desse tipo é um meio de construir tudo o que você provou a existência .
Uma característica importante do sabor construtivista é que a negação dupla não é equivalente à não negação. Na verdade, a negação raramente é um primitivo em um sistema de tipos, então, em vez disso, podemos representá-la como implicando falsidade, por exemplo,
not P
torna-seP -> Falsity
. A negação dupla seria, portanto, uma função com tipo(P -> Falsity) -> Falsity
, que claramente não é equivalente a algo do tipoP
.No entanto, há uma reviravolta interessante nisso! Em uma linguagem com polimorfismo paramétrico, as variáveis de tipo abrangem todos os tipos possíveis, incluindo os inabitados, portanto, um tipo totalmente polimórfico como
∀a. a
, em certo sentido, quase falso. E daí se escrevermos quase-negação dupla usando polimorfismo? Ficamos com um tipo que se parece com isso:∀a. (P -> a) -> a
. Isso é equivalente a algo do tipoP
? Na verdade, é , apenas aplique-o à função de identidade.Mas de que adianta? Por que escrever um tipo assim? Isso significa alguma coisa em termos de programação? Bem, você pode pensar nisso como uma função que já tem algo do tipo em
P
algum lugar e precisa que você dê a ela uma função que aceitaP
como um argumento, com a coisa toda sendo polimórfica no tipo de resultado final. Em certo sentido, representa uma computação suspensa , esperando que o resto seja fornecido. Nesse sentido, esses cálculos suspensos podem ser compostos juntos, passados, invocados, o que quer que seja. Isso deve começar a soar familiar para os fãs de algumas linguagens, como Scheme ou Ruby - porque o que significa é que a dupla negação corresponde ao estilo de passagem de continuaçãoe, de fato, o tipo que dei acima é exatamente a continuação da mônada em Haskell.fonte
P -> Falsity
. Eu entendo por que funciona (¬p ≡ p → ⊥), mas não entendi a versão do código.P -> ⊥
deve ser habitada justamente quandoP
não é, certo? Mas essa função não deveria ser sempre habitada? Ou possível nunca, na verdade, já que você não pode retornar uma instância de⊥
? Não vejo bem a condicionalidade disso. Qual é a intuição aqui?P -> Falsity
é equivalente aP
ser falsa.f x = x
, que seria instanciado sseP = ⊥
, mas isso claramente não era genérico o suficiente. Portanto, a ideia é que para retornar um tipo sem valor, você não precisa de nenhum corpo; mas para que a função seja definível e total, você não precisa de casos , então seP
for desabitado, tudo dá certo? Isso é um pouco estranho, mas acho que vejo. Isso parece interagir de forma bastante estranha com a minha definição doXor
tipo ... Vou ter que pensar sobre isso. Obrigado!Seu gráfico não está certo; em muitos casos, você confundiu tipos com termos.
[1] A lógica para uma linguagem funcional Turing-completa é inconsistente. A recursão não tem correspondência em teorias consistentes. Em uma teoria de prova lógica / incorreta, você poderia chamá-la de uma regra que causa inconsistência / falta de fundamento.
[2] Novamente, isso é uma consequência da integridade. Isso seria uma prova de um anti-teorema se a lógica fosse consistente - portanto, ela não pode existir.
[3] Não existe em linguagens funcionais, uma vez que elide características lógicas de primeira ordem: toda quantificação e parametrização são feitas sobre fórmulas. Se você tivesse características de primeira ordem, haveria um outro tipo do que
*
,* -> *
, etc .; o tipo de elementos do domínio do discurso. Por exemplo, emFather(X,Y) :- Parent(X,Y), Male(X)
,X
eY
abrangem o domínio do discurso (chame-oDom
), eMale :: Dom -> *
.fonte
fonte
Eu realmente gosto dessa pergunta. Não sei muito, mas tenho algumas coisas (auxiliado pelo artigo da Wikipedia , que tem algumas tabelas interessantes e tal):
Acho que os tipos de soma / tipos de união ( por exemplo
data Either a b = Left a | Right b
) são equivalentes à disjunção inclusiva . E, embora eu não esteja muito familiarizado com Curry-Howard, acho que isso demonstra. Considere a seguinte função:Se eu entendi as coisas corretamente, o tipo diz que ( a ∧ b ) → ( a ★ b ) e a definição diz que isso é verdade, onde ★ é inclusivo ou exclusivo ou, o que quer que
Either
represente. Você temEither
representante exclusivo ou, ⊕; entretanto, ( a ∧ b ) ↛ ( a ⊕ b ). Por exemplo, ⊤ ∧ ⊤ ≡ ⊤, mas ⊤ ⊕ ⊥ ≡ ⊥ e ⊤ ↛ ⊥. Em outras palavras, se ambos um e b são verdadeiras, então a hipótese é verdadeira, mas a conclusão é falsa, e por isso esta implicação deve ser falsa. No entanto, claramente, ( a ∧ b ) → ( a ∨ b ), desde que se ambos um e b forem verdadeiras, em seguida, pelo menos uma é verdadeiro. Assim, se as uniões discriminadas são alguma forma de disjunção, devem ser a variedade inclusiva. Acho que isso é uma prova, mas sinta-se mais do que livre para me desiludir dessa noção.Da mesma forma, suas definições para tautologia e absurdo como a função de identidade e funções não-terminantes, respectivamente, estão um pouco erradas. A verdadeira fórmula é representada pelo tipo de unidade , que é o tipo que tem apenas um elemento (
data ⊤ = ⊤
; frequentemente escrito()
e / ouUnit
em linguagens de programação funcionais). Isso faz sentido: como esse tipo tem garantia de ser habitado, e como só existe um habitante possível, deve ser verdade. A função de identidade representa apenas a tautologia particular que a → a .Seu comentário sobre funções não terminadas é, dependendo do que exatamente você quis dizer, mais errado. Curry-Howard funciona no sistema de tipo, mas a não terminação não é codificada lá. Segundo a Wikipedia , lidar com não-terminação é um problema, como a adição produz lógicas inconsistentes ( por exemplo , eu posso definir
wrong :: a -> b
porwrong x = wrong x
, e, assim, “provar” que a → b para qualquer um e b ). Se isso é o que você quis dizer com “absurdo”, então você está exatamente correto. Se em vez disso você quis dizer a declaração falsa, então o que você quer é qualquer tipo desabitado, por exemplo , algo definido pordata ⊥
- isto é, um tipo de dados sem nenhuma maneira de construí-lo. Isso garante que ele não tenha nenhum valor e, portanto, deve ser desabitado, o que é equivalente a falso. Eu acho que você provavelmente também poderia usara -> b
, já que se proibirmos funções que não terminam, então também está desabitado, mas não tenho 100% de certeza.A Wikipedia diz que os axiomas são codificados de duas maneiras diferentes, dependendo de como você interpreta Curry-Howard: ou nos combinadores ou nas variáveis. Acho que a visão combinadora significa que as funções primitivas que recebemos codificam as coisas que podemos dizer por padrão (semelhante ao modo como o modus ponens é um axioma porque a aplicação da função é primitiva). E eu acho que a visão variável pode realmente significar a mesma coisa - combinadores, afinal, são apenas variáveis globais que são funções particulares. Quanto aos tipos primitivos: se estou pensando sobre isso corretamente, então acho que os tipos primitivos são as entidades - os objetos primitivos sobre os quais estamos tentando provar coisas.
De acordo com minha classe de lógica e semântica, o fato de ( a ∧ b ) → c ≡ a → ( b → c ) (e também de b → ( a → c )) ser chamado de lei de equivalência de exportação, pelo menos na dedução natural provas. Não percebi na hora que estava apenas curry - gostaria de ter, porque isso é legal!
Embora agora tenhamos uma maneira de representar a disjunção inclusiva , não temos uma maneira de representar a variedade exclusiva. Devemos ser capazes de usar a definição de disjunção exclusiva para representá-lo: a ⊕ b ≡ ( a ∨ b ) ∧ ¬ ( a ∧ b ). Não sei escrever negação, mas sei que ¬ p ≡ p → ⊥, e tanto a implicação quanto a falsidade são fáceis. Devemos, portanto, ser capazes de representar disjunção exclusiva por:
Isso define
⊥
ser o tipo vazio sem valores, o que corresponde à falsidade;Xor
é então definido para conter ( e )Either
um a ou a b ( ou ) e uma função ( implicação ) de (a, b) ( e ) para o tipo inferior ( falso ).No entanto, não tenho ideia do que isso significa .( Edição 1: agora sim, veja o próximo parágrafo!)Como não há valores de tipo( Edição 1: Sim, camccann .)(a,b) -> ⊥
(existem?), Não consigo entender o que isso significaria em um programa. Alguém conhece uma maneira melhor de pensar sobre essa definição ou outra?Edição 1: Graças à resposta do camccann (mais particularmente, os comentários que ele deixou para me ajudar), acho que vejo o que está acontecendo aqui. Para construir um valor de tipo
Xor a b
, você precisa fornecer duas coisas. Primeiro, uma testemunha da existência de um elemento de uma
oub
como o primeiro argumento; ou seja, aLeft a
ou aRight b
. E em segundo lugar, uma prova de que não existem elementos de ambos os tiposa
eb
- em outras palavras, uma prova que(a,b)
é inabitada - como o segundo argumento. Já que você só poderá escrever uma função(a,b) -> ⊥
se(a,b)
estiver desabitada, o que significa ser esse o caso? Isso significaria que alguma parte de um objeto do tipo(a,b)
não poderia ser construído; em outras palavras, que pelo menos um, e possivelmente ambos,a
eb
são desabitadas bem! Nesse caso, se estivermos pensando em casamento de padrões, não é possível que haja correspondência de padrões em tal tupla: supondo que elab
seja desabitada, o que escreveríamos que pudesse corresponder à segunda parte dessa tupla? Portanto, não podemos corresponder a um padrão, o que pode ajudá-lo a ver por que isso o torna desabitado. Agora, a única maneira de ter uma função total que não aceita argumentos (como esta deve, uma vez que(a,b)
é inabitada) é o resultado ser de um tipo desabitado também - se estivermos pensando nisso de uma perspectiva de correspondência de padrões, isso significa que mesmo que a função não tenha casos, não há corpo possível poderia ter qualquer um, e então está tudo OK.Muito disso sou eu pensando em voz alta / provando (espero) coisas na hora, mas espero que seja útil. Eu realmente recomendo o artigo da Wikipedia ; Eu não li nenhum tipo de detalhe, mas suas tabelas são um resumo muito bom e muito completo.
fonte
Either a a
não deveria ser um teorema:Either ⊥ ⊥
ainda é desabitado. Tom: Como camccann disse, consistência implica rescisão. Assim, um sistema de tipos consistente não permitirá que você se expressef :: a -> b
e, portanto, o tipo seria desabitado; um sistema de tipo inconsistente teria um habitante para o tipo, mas um que não terminaria. camccann: Existem sistemas de tipos inconsistentes que não são Turing-completos, ocupando algum ponto intermediário na hierarquia? Ou essa última etapa (adicionar recursão geral ou qualquer outra coisa) é precisamente equivalente à inconsistência?Aqui está um um pouco obscuro que estou surpreso não ter sido mencionado antes: a programação reativa funcional "clássica" corresponde à lógica temporal.
Claro, a menos que você seja um filósofo, matemático ou um programador funcional obsessivo, isso provavelmente levanta várias outras questões.
Então, primeiro: o que é programação reativa funcional? É uma maneira declarativa de trabalhar com valores que variam no tempo . Isso é útil para escrever coisas como interfaces de usuário porque as entradas do usuário são valores que variam com o tempo. O FRP "clássico" tem dois tipos básicos de dados: eventos e comportamentos.
Os eventos representam valores que existem apenas em momentos discretos. As teclas são um ótimo exemplo: você pode pensar nas entradas do teclado como um caractere em um determinado momento. Cada tecla pressionada é então apenas um par com o caractere da tecla e a hora em que foi pressionada.
Os comportamentos são valores que existem constantemente, mas podem mudar continuamente. A posição do mouse é um ótimo exemplo: é apenas um comportamento das coordenadas x, y. Afinal, o mouse sempre tem uma posição e, conceitualmente, essa posição muda continuamente à medida que você move o mouse. Afinal, mover o mouse é uma ação única e prolongada, não um monte de etapas discretas.
E o que é lógica temporal? Apropriadamente, é um conjunto de regras lógicas para lidar com proposições quantificadas ao longo do tempo. Essencialmente, ele estende a lógica normal de primeira ordem com dois quantificadores: □ e ◇. O primeiro significa "sempre": leia □ φ como "φ sempre é válido". A segunda é "eventualmente": ◇ φ significa que "φ eventualmente aguentará". Este é um tipo particular de lógica modal . As duas leis a seguir relacionam os quantificadores:
Portanto, □ e ◇ são duais entre si da mesma forma que ∀ e ∃.
Esses dois quantificadores correspondem aos dois tipos de FRP. Em particular, □ corresponde a comportamentos e ◇ corresponde a eventos. Se pensarmos em como esses tipos são habitados, isso deveria fazer sentido: um comportamento é habitado em todos os momentos possíveis, enquanto um evento só acontece uma vez.
fonte
Relacionado à relação entre continuações e dupla negação, o tipo de chamada / cc é a lei de Peirce http://en.wikipedia.org/wiki/Call-with-current-continuation
CH é geralmente declarado como correspondência entre a lógica intuicionista e os programas. No entanto, se adicionarmos o operador call-with-current-continuation (callCC) (cujo tipo corresponde à lei de Peirce), obtemos uma correspondência entre a lógica clássica e os programas com callCC.
fonte
Embora não seja um isomorfismo simples, esta discussão sobre LEM construtivo é um resultado muito interessante. Em particular, na seção de conclusão, Oleg Kiselyov discute como o uso de mônadas para obter eliminação de dupla negação em uma lógica construtiva é análogo a distinguir proposições computacionalmente decidíveis (para as quais LEM é válido em um ambiente construtivo) de todas as proposições. A noção de que as mônadas capturam efeitos computacionais é antiga, mas esta instância do isomorfismo Curry-Howard ajuda a colocá-la em perspectiva e ajuda a entender o que a dupla negação realmente "significa".
fonte
O suporte de continuação de primeira classe permite que você expresse $ P \ lor \ neg P $. O truque se baseia no fato de que não chamar a continuação e sair com alguma expressão é equivalente a chamar a continuação com essa mesma expressão.
Para uma visão mais detalhada, consulte: http://www.cs.cmu.edu/~rwh/courses/logic/www-old/handouts/callcc.pdf
fonte
Uma coisa que é importante, mas ainda não foi investigada, é a relação de 2 continuação (continuação que leva 2 parâmetros) e AVC de Sheffer . Na lógica clássica, o traço de Sheffer pode formar um sistema lógico completo por si só (mais alguns conceitos de não-operador). O que significa que o familiar
and
,or
,not
podem ser implementados utilizando apenas o Stoke Sheffer ounand
.Este é um fato importante de sua correspondência de tipo de programação porque avisa que um único combinador de tipo pode ser usado para formar todos os outros tipos.
A assinatura de tipo de uma continuação 2 é
(a,b) -> Void
. Por esta implementação, podemos definir 1-continuação (continuações normais) como(a,a)
-> Vazio, tipo de produto como((a,b)->Void,(a,b)->Void)->Void
, tipo de soma como((a,a)->Void,(b,b)->Void)->Void
. Isso nos dá uma impressionante força de expressividade.Se cavarmos mais, descobriremos que o grafo existencial de Piece é equivalente a uma linguagem com o único tipo de dados é n-continuação, mas não vi nenhuma linguagem existente nesta forma. Então, inventar um pode ser interessante, eu acho.
fonte