Correspondência de Curry Howard com Predicate Logic?

7

Então, eu estou tentando convencer Curry-Howard. (Eu tentei várias vezes, não é gelificante / parece muito abstrato). Para abordar algo concreto, estou trabalhando nos dois tutoriais de Haskell vinculados à wikipedia, especialmente no de Tim Newsham . Também há uma discussão útil quando Newsham postou o tutorial.

(Mas vou ignorar os dataenvoltórios de Newsham e Piponi e falar sobre os tipos subjacentes.) Temos o esquema de axiomas de Hilbert (expresso como combinadores S , K ); nós temos proposições como tipos; implicação como função-seta; e Modus Ponens como aplicação de função:

axK :: p -> q -> p
axK  = const
axS :: (p -> q -> r) -> (p -> q) -> p -> r
axS     f                g          x  = f x (g x)

modPons = ($);   infixl 0 `modPons`          -- infix Left, cp ($) is Right

Então eu posso derivar a lei de identidade:

ident     = axS `modPons` axK `modPons` axK  -- (S K K)
-- ident :: p -> p                           -- inferred

Ter esses tipos como caracteres simples, apenas correspondendo a proposições, parece um pouco sem imaginação. Posso usar mais do sistema de tipos para realmente construir as proposições? Estou pensando:

data      IsNat n  = IsNat !n                -- [Note **]

data      Z        = Z
axNatZ ::            IsNat Z
axNatZ             = IsNat Z

data      S n      = S !n
axNatS :: IsNat n -> IsNat (S n)
axNatS   (IsNat n) = IsNat (S n) 

twoIsNat = axNatS `modPons` (axNatS `modPons` axNatZ)
-- ===> IsNat (S (S Z))

[Nota **] Estou usando construtores rigorosos, conforme o tópico da discussão, para evitar a introdução de _ | _ .

Onde:

  • IsNat é um predicado: fazer uma proposição a partir de um termo.
  • n é uma variável
  • S é uma função, criando um termo a partir de uma variável.
  • Z é uma constante (função niládica).

Então eu pareço ter incorporado a lógica de predicados (de primeira ordem) (?)

Aprecio que meus tipos não são muito higiênicos; Eu poderia facilmente misturar uma typevar-como-proposição com uma typevar-como-termo. Talvez eu deva usar o Kindsistema para segregá-los. OTOH meus axiomas teriam que estar espetacularmente errados para chegar a qualquer conclusão.

Eu não expressei:

  • quantificador universal: está implícito nos vars livres;
  • quant existencial: de fato, as constantes poderiam atuar como existenciais skememised;
  • igualdade de termos: usei repetidos tipos de letra em implicações;
  • relações: isso parece funcionar, ou é mancada? ...
    data          PlusNat n m l  = PlusNat !n !m !l

    axPlusNatZ :: IsNat m       -> PlusNat Z m m
    axPlusNatZ   (IsNat m)       = PlusNat Z m m

    axPlusNatS :: PlusNat n m l -> PlusNat (S n) m (S l)
    axPlusNatS   (PlusNat n m l) = PlusNat (S n) m (S l)

    plus123 = axPlusNatS `modPons`
              (axPlusNatZ `modPons`
               (axNatS `modPons` (axNatS `modPons` axNatZ)) ) 
    -- ===> PlusNat (S Z) (S (S Z)) (S (S (S Z)))

Escrever os axiomas é fácil, cortesia dos Teoremas de Wadler de graça! . Escrever as provas é um trabalho árduo. (Vou largar o modPonse apenas usar o aplicativo de função.)

Isso é realmente alcançar uma lógica? Ou é uma coisa louca? Devo parar antes de causar mais danos ao meu cérebro?

Você precisa de Tipos Dependentes para expressar FOPL em Curry-Howard. Mas eu não pareço estar fazendo isso (?)

AntC
fonte
11
O problema com sua abordagem é IsNatnão fazer uma proposição a partir de um termo, é fazer uma proposição a partir de uma proposição .
Derek Elkins saiu de SE
Obrigado @DerekElkins, acho que você quer dizer que o argumento IsNaté apenas um tipo, portanto deve ser uma proposição. OK, igualmente IsNat né apenas um tipo, portanto deve ser uma proposição. Devo estar "em minha honra" para não deixar nescapar na terra da proposição / aparecer como argumento para um conectivo lógico (e foi por isso que falei sobre higiene do tipo). Você ficaria mais feliz se eu usasse a codificação da Igreja para Nats? Eu acho que só estou estendendo λ-calc com construtores no nível do tipo, mesmo que Haskell ao nível prazo (?)
ANTC
PS talvez n seja uma proposição : está dizendo 'eu sou habitado'. O que não é mais do que qualquer typevar está dizendo sob CH. IsNat nestá dizendo / testemunhando: além disso, o habitante de né de um 'tipo' particular, também conhecido como 'tipo' na lógica. Então eu estou indo além de simplesmente tipado λ-calc (?)
ANTC
11
Eu não acho que aprender sobre o isomorfismo de Curry-Howard brincando com linguagens de programação misteriosas é a abordagem correta. Não vai levar a uma verdadeira compreensão do princípio.
Rout de milhas
11
Se você não entende os textos, deve aprender o que precisa para entender os textos. Haskell não é uma dessas coisas.
Rout de milhas

Respostas:

3

Para explicar por que não me sinto à vontade com os datainvólucros de Newsham e (especialmente) de Piponi ... (Isso será mais uma pergunta do que uma resposta, mas talvez isso funcione para explicar o que há de errado com o meu, IsNatmesmo que pareça muito com o Newsham. )

A página 17 de Piponi possui:

data Proposition = Proposition :-> Proposition
                  | Symbol String
                  | False deriving Eq

data Proof = MP Proof Proof
     | Axiom String Proposition deriving Eq

Não vejo tipos aqui. Ortografia de um construtor de dados :->não faz com que ela funcione como seta; e a ortografia de um construtor de dados MP(para o Modus Ponens) não faz com que ele funcione como aplicativo. Há um operador de 'construtor inteligente' (@@)para MP, mas que não se aplica nenhuma função: ele meramente faz correspondência de padrão no :->construtor.

Newsham possui (começarei com a seção Implication / Modus Ponens):

data Prop p = Prop p

data p :=> q = Imp (Prop p -> Prop q)

impInj :: (Prop p -> Prop q) -> Prop (p :=> q)
impInj p2q = Prop (Imp p2q)

impElim :: Prop p -> Prop (p :=> q) -> Prop q
impElim p (Prop (Imp p2q)) = p2q p

Parece mais com isso: temos tipos, setas de função, aplicação de função impElim. O construtor de tipos, Impcom suas regras de injeção e eliminação, reflete as estruturas das árvores de prova no isomorfismo Lectures on Curry-Howard . Mas estou nervoso: por que Impprecisa de um construtor de tipos e de dados? Mais uma vez, ortografia como :=>não faz disso uma seta de função. Por que todos esses Propconstrutores de embrulhar e desembrulhar ? Por que não é simples Prop (p -> q)?

Então, quando olho para a seção 'Conjunção' (que realmente vem primeiro):

data p :/\ q = And (Prop p) (Prop q)

andInj :: Prop p -> Prop q -> Prop (p :/\ q)
andInj p q = Prop (And p q)

andElimL :: Prop (p :/\ q) -> Prop p
andElimL (Prop (And p q)) = p

andElimR :: Prop (p :/\ q) -> Prop q
andElimR (Prop (And p q)) = q

Essas Elimfunções não usam aplicativo de função. Eles meramente correspondem aos construtores. Não há estrutura implicacional para a conjunção: confiamos inteiramente que o 'programador' usou apenas a andInjregra para criar um tipo (p :/\ q).

Então, quando Newsham chega à comutatividade da conjunção:

commuteAnd :: Prop (p :/\ q) -> Prop (q :/\ p)
commuteAnd pq = andInj (andElimR pq) (andElimL pq)

E reivindicações

Observe que nossa prova de Haskell não contém nenhuma referência à estrutura interna do tipo de dados (: / \). Tendo definido e comprovado nossas regras "andInj", "andElimR" e "andElimL", nunca precisaríamos espiar a implementação do tipo de dados (: / \) novamente.

Eu simplesmente discordo:

  • A assinatura para commuteAnd não contar com a estrutura interna do :/\construtor de tipo.
  • Embora a aparência de definição de função como mera aplicação de função, na verdade, as andElims fazer "espiada" na estrutura.

Eu esperaria que pudéssemos provar a comutatividade da conjunção a partir de sua definição, sem precisar de um axioma para dizer isso (?)

OK, se estou sendo tão purista, o que espero? Isso para conjunção é baseado na codificação da Igreja para par:

type Conj p q r = Prop ((p -> q -> r) -> r)     -- not right, see later

andInj :: Prop p -> Prop q -> Conj p q r
andInj (Prop p) (Prop q) = Prop (\elim -> elim p q)

andElimL :: Conj p q p -> Prop p
andElimL (Prop conj) = Prop (conj axK)    -- axK is `const`

Agora eu posso escrever commuteAndusando o aplicativo de função 'adequada':

--  commuteAnd :: (Conj p q r) -> (Conj q p r)
commuteAnd pq = andInj (andElimR pq) (andElimL pq)

Essa definição de função é a mesma que a de Newsham. Mas eu comentei a assinatura porque o tipo inferido de GHC não é suficientemente geral. Ele quer Prop ((p -> p -> p) -> p) -> ((p -> p -> p) -> p). O que é apenas uma variação sofisticada da lei de identidade.

Edit: (Após o primeiro comentário de @DerekElkins aqui.) Recuse tudo isso:

Acabei em águas profundas (e turvas). Eu acho que meu tipo de Conjnecessidade deve ser mais polimórfico, possivelmente impredicativo. Eu tentei dar uma classificação mais alta:

type Conj p q = Prop (forall r.((p -> q -> r) -> r))

Mas o GHC não está jogando. (E o suporte ao polimorfismo impredicativo é "esquisito".) E forallalgo assim quantificado parece suspeito com a definição de Falso / desabitado que eu esperava usar.

Eu preciso de um tipo de classificação mais alta, mas não impredicativo. E não no tipo para, Conjmas paracommuteAnd

commuteAnd :: (forall r. Conj p q r) -> (Conj q p r')
-- (same function for `commuteAnd` as above)

ESTÁ BEM. Felizmente, posso trocar argumentos ad infinitum.

[Fim da edição]

Então, minha pergunta: se é legítimo o que Newsham está fazendo com construtores de aninhamento e correspondência de padrões em todos os lugares, o que não é legítimo na minha IsNatcorrespondência de padrões e?

AntC
fonte
11
Como Piponi afirma, os tipos Propositione Proofnão são exemplos da correspondência de Curry-Howard em ação. Esses apenas formam um sistema simples de prova no estilo LCF. A correspondência Curry-Howard é testemunhado pela compilefunção (embora a maior parte do trabalho é feito por (^)e show) que transforma esses Propositions e Proofs em código Haskell. Como para o código de Newsham, os invólucros não fazem diferença (:=>)é essencialmente (->). Ele apenas os adiciona (como afirma explicitamente) para que ele possa trocar uma implementação diferente e não trivial posteriormente.
Derek Elkins saiu de SE
Obrigado. Na Neasham, eu queria seguir a lógica intuicionista, não a clássica; então ignorou as continuações / coisas da mônada. Você está dizendo que os padrões dele não funcionam para os intuicionistas? Sim (:=>)é implementado como (->). Mas um modelo / simulação de uma lógica, precisa de uma validação do intérprete (?). Por que não posso expressar diretamente em Haskell (->)?
AntC 17/07/19
Comentários semelhantes escritos por Piponi: ele apresenta (^)para lidar com o clássico. OK, vou tentar o seu showpara gerar código Haskell e ver como isso difere. (Também tentarei descobrir como ele está apresentando Integer.) Estou tentando usar um mínimo de máquinas Haskell, por medo de apresentar _|_algum lugar e minar "programas são provas".
AntC 17/07/19