Qual è l'equivalente logico combinatorio della teoria dei tipi intuizionista?


87

Recentemente ho completato un corso universitario che prevedeva Haskell e Agda (un linguaggio di programmazione funzionale tipizzato dipendente) e mi chiedevo se fosse possibile sostituire il lambda calcolo in questi con la logica combinatoria. Con Haskell questo sembra possibile utilizzando i combinatori S e K, rendendolo così privo di punti. Mi chiedevo quale fosse l'equivalente per Agda. Vale a dire, si può creare un linguaggio di programmazione funzionale tipizzato in modo dipendente equivalente ad Agda senza utilizzare variabili?

Inoltre, è possibile sostituire in qualche modo la quantificazione con i combinatori? Non so se sia una coincidenza, ma la quantificazione universale, ad esempio, fa sembrare una firma di tipo un'espressione lambda. C'è un modo per rimuovere la quantificazione universale da una firma di tipo senza modificarne il significato? Ad esempio in:

forall a : Int -> a < 0 -> a + a < a

Si può esprimere la stessa cosa senza usare un forall?


21
Inizia individuando i tipi più dipendenti possibili per K (facile) e S (piuttosto peloso). Sarebbe interessante inserire le costanti per Set e Pi, quindi provare a ricostruire il sistema di base (incoerente) Set: Set. Ci penserei ancora, ma ho un aereo da prendere.
Pigworker

Risposte:


52

Quindi ci ho pensato un po 'di più e ho fatto dei progressi. Ecco un primo tentativo di codificare il sistema deliziosamente semplice (ma incoerente) di Martin-Löf Set : Setin uno stile combinatorio. Non è un buon modo per finire, ma è il posto più facile per iniziare. La sintassi di questa teoria dei tipi è semplicemente lambda-calcolo con annotazioni di tipo, tipi Pi e un set di universi.

La teoria del tipo di bersaglio

Per completezza, vi presento le regole. La validità del contesto dice solo che puoi costruire contesti dal vuoto aggiungendo nuove variabili che abitano Sets.

                     G |- valid   G |- S : Set
--------------     ----------------------------- x fresh for G
  . |- valid         G, x:S |- valid

E ora possiamo dire come sintetizzare i tipi per i termini in un dato contesto e come cambiare il tipo di qualcosa fino al comportamento computazionale dei termini che contiene.

  G |- valid             G |- S : Set   G |- T : Pi S \ x:S -> Set
------------------     ---------------------------------------------
  G |- Set : Set         G |- Pi S T : Set

  G |- S : Set   G, x:S |- t : T x         G |- f : Pi S T   G |- s : S
------------------------------------     --------------------------------
  G |- \ x:S -> t : Pi S T                 G |- f s : T s

  G |- valid                  G |- s : S   G |- T : Set
-------------- x:S in G     ----------------------------- S ={beta} T
  G |- x : S                  G |- s : T

In una piccola variazione rispetto all'originale, ho reso lambda l'unico operatore vincolante, quindi il secondo argomento di Pi dovrebbe essere una funzione che calcola il modo in cui il tipo restituito dipende dall'input. Per convenzione (ad esempio in Agda, ma purtroppo non in Haskell), l'ambito di lambda si estende verso destra il più lontano possibile, quindi puoi spesso lasciare le astrazioni senza parentesi quando sono l'ultimo argomento di un operatore di ordine superiore: puoi vedere che l'ho fatto quello con Pi. Il tuo tipo Agda (x : S) -> Tdiventa Pi S \ x:S -> T.

( Digressione . Le annotazioni di tipo su lambda sono necessarie se vuoi essere in grado di sintetizzare il tipo di astrazioni. Se passi al controllo del tipo come modus operandi, hai ancora bisogno di annotazioni per controllare un beta-redex come (\ x -> t) s, poiché non hai modo per indovinare i tipi delle parti da quello del tutto. Consiglio ai designer moderni di controllare i tipi ed escludere i beta-redex dalla stessa sintassi.

( Digressione . Questo sistema è incoerente in quanto Set:Setconsente la codifica di una varietà di "paradossi bugiardi". Quando Martin-Löf ha proposto questa teoria, Girard gli ha inviato una codifica nel proprio Sistema U incoerente. Il successivo paradosso dovuto a Hurkens è il la più pulita costruzione tossica che conosciamo.)

Sintassi e normalizzazione del combinatore

Comunque, abbiamo due simboli extra, Pi e Set, quindi potremmo forse gestire una traduzione combinatoria con S, K e due simboli extra: ho scelto U per l'universo e P per il prodotto.

Ora possiamo definire la sintassi combinatoria non tipizzata (con variabili libere):

data SKUP = S | K | U | P deriving (Show, Eq)

data Unty a
  = C SKUP
  | Unty a :. Unty a
  | V a
  deriving (Functor, Eq)
infixl 4 :.

Nota che ho incluso i mezzi per includere variabili libere rappresentate dal tipo ain questa sintassi. Oltre ad essere un riflesso da parte mia (ogni sintassi degna di questo nome è una monade libera con returnvariabili incorporanti e >>=sostituzione di esecuzione), sarà utile rappresentare fasi intermedie nel processo di conversione dei termini con associazione alla loro forma combinatoria.

Ecco la normalizzazione:

norm :: Unty a -> Unty a
norm (f :. a)  = norm f $. a
norm c         = c

($.) :: Unty a -> Unty a -> Unty a        -- requires first arg in normal form
C S :. f :. a $. g  = f $. g $. (a :. g)  -- S f a g = f g (a g)   share environment
C K :. a $. g       = a                   -- K a g = a             drop environment
n $. g              = n :. norm g         -- guarantees output in normal form
infixl 4 $.

(Un esercizio per il lettore è definire un tipo esattamente per le forme normali e affinare i tipi di queste operazioni.)

Rappresentare la teoria dei tipi

Possiamo ora definire una sintassi per la nostra teoria dei tipi.

data Tm a
  = Var a
  | Lam (Tm a) (Tm (Su a))    -- Lam is the only place where binding happens
  | Tm a :$ Tm a
  | Pi (Tm a) (Tm a)          -- the second arg of Pi is a function computing a Set
  | Set
  deriving (Show, Functor)
infixl 4 :$

data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"

data Su a = Ze | Su a deriving (Show, Functor, Eq)

Uso una rappresentazione dell'indice di de Bruijn alla maniera Bellegarde e Hook (resa popolare da Bird e Paterson). Il tipo Su aha un elemento in più rispetto a a, e lo usiamo come il tipo di variabili libere sotto un legante, con Zecome variabile appena associata ed Su xessendo la rappresentazione spostata della vecchia variabile libera x.

Tradurre i termini in combinatori

Fatto ciò, acquisiamo la solita traduzione, basata sull'astrazione delle parentesi .

tm :: Tm a -> Unty a
tm (Var a)    = V a
tm (Lam _ b)  = bra (tm b)
tm (f :$ a)   = tm f :. tm a
tm (Pi a b)   = C P :. tm a :. tm b
tm Set        = C U

bra :: Unty (Su a) -> Unty a               -- binds a variable, building a function
bra (V Ze)      = C S :. C K :. C K        -- the variable itself yields the identity
bra (V (Su x))  = C K :. V x               -- free variables become constants
bra (C c)       = C K :. C c               -- combinators become constant
bra (f :. a)    = C S :. bra f :. bra a    -- S is exactly lifted application

Digitando i combinatori

La traduzione mostra il modo in cui utilizziamo i combinatori, il che ci fornisce un indizio su quali dovrebbero essere i loro tipi. Ue Psono solo costruttori impostati, quindi, scrivendo tipi non tradotti e consentendo la "notazione Agda" per Pi, dovremmo avere

U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set

Il Kcombinatore viene utilizzato per elevare un valore di un tipo Aa una funzione costante rispetto a un altro tipo G.

  G : Set   A : Set
-------------------------------
  K : (a : A) -> (g : G) -> A

Il Scombinatore viene utilizzato per sollevare le applicazioni su un tipo, dal quale possono dipendere tutte le parti.

  G : Set
  A : (g : G) -> Set
  B : (g : G) -> (a : A g) -> Set
----------------------------------------------------
  S : (f : (g : G) ->    (a : A g) -> B g a   ) ->
      (a : (g : G) ->    A g                  ) ->
           (g : G) ->    B g (a g)

Se guardi il tipo di S, vedrai che afferma esattamente la regola di applicazione contestualizzata della teoria dei tipi, quindi questo è ciò che lo rende adatto a riflettere il costrutto dell'applicazione. È il suo lavoro!

Abbiamo quindi applicazione solo per cose chiuse

  f : Pi A B
  a : A
--------------
  f a : B a

Ma c'è un intoppo. Ho scritto i tipi dei combinatori nella teoria dei tipi ordinaria, non nella teoria dei tipi combinatori. Fortunatamente, ho una macchina che farà la traduzione.

Un sistema di tipo combinatorio

---------
  U : U

---------------------------------------------------------
  P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))

  G : U
  A : U
-----------------------------------------
  K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))

  G : U
  A : P[G](KU)
  B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
--------------------------------------------------------------------------------------
  S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
      (S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
      (S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
      (S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
      (S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
      (S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
      (S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))

  M : A   B : U
----------------- A ={norm} B
  M : B

Quindi il gioco è fatto, in tutta la sua gloria illeggibile: una presentazione combinatoria di Set:Set!

C'è ancora un piccolo problema. La sintassi del sistema ti dà modo di indovinare la G, Ae Bparametri per Se allo stesso modo per K, solo dai termini. Di conseguenza, possiamo verificare algoritmicamente le derivazioni di battitura , ma non possiamo semplicemente controllare i termini del combinatore come avremmo potuto con il sistema originale. Ciò che potrebbe funzionare è richiedere l'input al tipografo per portare annotazioni di tipo sugli usi di S e K, registrando efficacemente la derivazione. Ma questa è un'altra lattina di vermi ...

Questo è un buon posto dove fermarsi, se sei stato abbastanza entusiasta di iniziare. Il resto è roba "dietro le quinte".

Generazione dei tipi di combinatori

Ho generato quei tipi combinatori usando la traduzione dell'astrazione delle parentesi dai termini rilevanti della teoria dei tipi. Per mostrare come ho fatto e rendere questo post non del tutto inutile, lasciami offrire la mia attrezzatura.

Posso scrivere i tipi dei combinatori, completamente astratti sui loro parametri, come segue. Uso la mia pratica pilfunzione, che combina Pi e lambda per evitare di ripetere il tipo di dominio, e piuttosto utilmente mi consente di utilizzare lo spazio delle funzioni di Haskell per legare le variabili. Forse puoi quasi leggere quanto segue!

pTy :: Tm a
pTy = fmap magic $
  pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set

kTy :: Tm a
kTy = fmap magic $
  pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A

sTy :: Tm a
sTy = fmap magic $
  pil Set $ \ _G ->
  pil (pil _G $ \ g -> Set) $ \ _A ->
  pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
  pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
  pil (pil _G $ \ g -> _A :$ g) $ \ a ->
  pil _G $ \ g -> _B :$ g :$ (a :$ g)

Con questi definiti, ho estratto i sottotermini aperti pertinenti e li ho passati alla traduzione.

Un toolkit di codifica de Bruijn

Ecco come costruire pil. In primo luogo, definisco una classe di Fininsiemi di ite, usata per le variabili. Ognuno di questi set ha un embedding che preserva il costruttore nel set sopra, più un nuovo topelemento, e puoi distinguerli: la embdfunzione ti dice se un valore è nell'immagine di emb.

class Fin x where
  top :: Su x
  emb :: x -> Su x
  embd :: Su x -> Maybe x

Possiamo, ovviamente, istanziare Finper ZeeSuc

instance Fin Ze where
  top = Ze              -- Ze is the only, so the highest
  emb = magic
  embd _ = Nothing      -- there was nothing to embed

instance Fin x => Fin (Su x) where
  top = Su top          -- the highest is one higher
  emb Ze     = Ze            -- emb preserves Ze
  emb (Su x) = Su (emb x)    -- and Su
  embd Ze      = Just Ze           -- Ze is definitely embedded
  embd (Su x)  = fmap Su (embd x)  -- otherwise, wait and see

Ora posso definire minore o uguale, con un'operazione di indebolimento .

class (Fin x, Fin y) => Le x y where
  wk :: x -> y

La wkfunzione dovrebbe incorporare gli elementi di xcome gli elementi più grandi di y, in modo che le cose extra ysiano più piccole, e quindi in termini di indice di de Bruijn, legate più localmente.

instance Fin y => Le Ze y where
  wk = magic    -- nothing to embed

instance Le x y => Le (Su x) (Su y) where
  wk x = case embd x of
    Nothing  -> top          -- top maps to top
    Just y   -> emb (wk y)   -- embedded gets weakened and embedded

E una volta che hai risolto questo problema, un po 'di roba da poco fa il resto.

lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)

La funzione di ordine superiore non ti dà solo un termine che rappresenta la variabile, ti dà una cosa sovraccaricata che diventa la rappresentazione corretta della variabile in qualsiasi ambito in cui la variabile è visibile. Cioè, il fatto che mi prenda la briga di distinguere i diversi ambiti per tipo fornisce al tipografo Haskell informazioni sufficienti per calcolare lo spostamento richiesto per la traduzione nella rappresentazione di de Bruijn. Perché tenere un cane e abbaiare da soli?


questo potrebbe essere super stupido, ma come cambia questa immagine se aggiungi il Fcombinatore? Fagisce in modo diverso a seconda del suo primo argomento: se Aè un atomo, Me Nsono termini ed PQè un composto, allora FAMN -> Me F(PQ)MN -> NPQ. Questo non può essere rappresentato nel SK(I)calcolo ma Kpuò essere rappresentato come FF. È possibile estendere il tuo MLTT senza punti con questo?
kram1032

Sono abbastanza sicuro che ci sia un problema con questa procedura di astrazione delle parentesi, in particolare la parte "combinatori diventano costanti", che traduce λx.c in Kc per i combinatori c ∈ {S, K, U, P}. Il problema è che questi combinatori sono polimorfici e possono essere usati in un tipo che dipende da x; un tale tipo non può essere preservato da questa traduzione. Come esempio concreto, viene tradotto il termine λ (A : Set) → λ (a : A) → adi tipo , che non può essere utilizzato in un tipo in cui il tipo del secondo argomento dipende dal primo argomento. (A : Set) → (a : A) → AS(S(KS)(KK))(KK)
Anders Kaseorg

8

Immagino che "Astrazione con parentesi" funzioni anche per i tipi dipendenti in alcune circostanze. Nella sezione 5 del documento seguente trovi alcuni tipi K e S:

Coincidenze oltraggiose ma significative
Sintassi indipendente dai tipi e valutazione
Conor McBride, University of Strathclyde, 2010

Convertire un'espressione lambda in un'espressione combinatoria corrisponde grosso modo alla conversione di una dimostrazione di deduzione naturale in una dimostrazione in stile Hilbert.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.