Théorie des types dépendants et fonctions de type «arbitraires»
Ma première réponse à cette question était élevée sur les concepts et faible sur les détails et réfléchie à la sous-question, «que se passe-t-il?»; cette réponse sera la même mais concentrée sur la sous-question, «pouvons-nous obtenir des fonctions de type arbitraire?».
Une extension des opérations algébriques de somme et de produit sont les soi-disant `` grands opérateurs '', qui représentent la somme et le produit d'une séquence (ou plus généralement, la somme et le produit d'une fonction sur un domaine) généralement écrits Σ
et Π
respectivement. Voir la notation Sigma .
Donc la somme
a₀ + a₁X + a₂X² + ...
pourrait être écrit
Σ[i ∈ ℕ]aᵢXⁱ
où a
est une séquence de nombres réels, par exemple. Le produit serait représenté de manière similaire avec Π
au lieu de Σ
.
Lorsque vous regardez de loin, ce type d'expression ressemble beaucoup à une fonction «arbitraire» dans X
; nous sommes bien entendu limités aux séries exprimables et à leurs fonctions analytiques associées. Est-ce un candidat pour une représentation dans une théorie des types? Absolument!
La classe des théories de type qui ont des représentations immédiates de ces expressions est la classe des théories de type «dépendantes»: les théories avec des types dépendants. Naturellement, nous avons des termes qui dépendent des termes, et dans des langages comme Haskell avec des fonctions de type et une quantification de type, des termes et des types selon les types. Dans un cadre dépendant, nous avons en outre des types selon les termes. Haskell n'est pas un langage typé de manière dépendante, bien que de nombreuses fonctionnalités des types dépendants puissent être simulées en torturant un peu le langage .
Curry-Howard et types dépendants
L'isomorphisme de Curry-Howard a commencé sa vie comme une observation que les termes et les règles de jugement de type du calcul lambda simplement typé correspondent exactement à la déduction naturelle (telle que formulée par Gentzen) appliquée à la logique propositionnelle intuitionniste, les types tenant lieu de propositions et des termes remplaçant les preuves, bien que les deux soient inventés / découverts indépendamment. Depuis lors, il a été une énorme source d'inspiration pour les théoriciens des types. L'une des choses les plus évidentes à considérer est de savoir si, et comment, cette correspondance pour la logique propositionnelle peut être étendue aux logiques de prédicat ou d'ordre supérieur. Les théories de type dépendantes sont d'abord nées de cette voie d'exploration.
Pour une introduction à l'isomorphisme de Curry-Howard pour le calcul lambda de type simple, voir ici . Par exemple, si nous voulons prouver, A ∧ B
nous devons prouver A
et prouver B
; une preuve combinée est simplement une paire de preuves: une pour chaque conjoint.
En déduction naturelle:
Γ ⊢ A Γ ⊢ B
Γ ⊢ A ∧ B
et dans le calcul lambda simplement tapé:
Γ ⊢ a : A Γ ⊢ b : B
Γ ⊢ (a, b) : A × B
Des correspondances similaires existent pour les ∨
types de somme et de somme, →
et les types de fonction, et les différentes règles d'élimination.
Une proposition non démontrable (intuitivement fausse) correspond à un type inhabité.
En gardant à l'esprit l'analogie des types comme propositions logiques, nous pouvons commencer à réfléchir à la façon de modéliser les prédicats dans le monde des types. Il existe de nombreuses façons de formaliser cela (voir cette introduction à la théorie des types intuitionnistes de Martin-Löf pour une norme largement utilisée), mais l'approche abstraite observe généralement qu'un prédicat est comme une proposition avec des variables de terme libres, ou, alternativement, une fonction prenant des termes pour des propositions. Si nous permettons aux expressions de type de contenir des termes, alors un traitement dans le style du calcul lambda se présente immédiatement comme une possibilité!
En ne considérant que les preuves constructives, de quoi constitue une preuve ∀x ∈ X.P(x)
? Nous pouvons la considérer comme une fonction de preuve, en prenant des termes ( x
) aux preuves de leurs propositions correspondantes ( P(x)
). Ainsi , les membres (preuves) du type (proposition) ∀x : X.P(x)
sont des « fonctions dépendantes », qui , pour chaque x
à X
donner un terme de type P(x)
.
Et alors ∃x ∈ X.P(x)
? Nous avons besoin de tout membre X
, x
ainsi qu'une preuve de P(x)
. Les membres (preuves) du type (proposition) ∃x : X.P(x)
sont donc des «paires dépendantes»: un terme distingué x
dans X
, avec un terme de type P(x)
.
Notation: je vais utiliser
∀x ∈ X...
pour les déclarations réelles sur les membres de la classe X
, et
∀x : X...
pour les expressions de type correspondant à la quantification universelle sur le type X
. De même pour ∃
.
Considérations combinatoires: produits et sommes
En plus de la correspondance de Curry-Howard des types avec propositions, nous avons la correspondance combinatoire des types algébriques avec des nombres et des fonctions, qui est le point principal de cette question. Heureusement, cela peut être étendu aux types dépendants décrits ci-dessus!
Je vais utiliser la notation module
|A|
représenter la «taille» d'un type A
, rendre explicite la correspondance décrite dans la question, entre les types et les nombres. Notez que c'est un concept en dehors de la théorie; Je ne prétends pas qu'il doit y avoir un tel opérateur dans la langue.
Comptons les membres possibles (entièrement réduits, canoniques) de type
∀x : X.P(x)
qui est le type de fonctions dépendantes prenant des termes x
de type X
en termes de type P(x)
. Chacune de ces fonctions doit avoir une sortie pour chaque terme de X
, et cette sortie doit être d'un type particulier. Pour chaque x
dans X
, puis, cela donne |P(x)|
« choix » de sortie.
La punchline est
|∀x : X.P(x)| = Π[x : X]|P(x)|
ce qui bien sûr n'a pas beaucoup de sens si X
c'est le cas IO ()
, mais est applicable aux types algébriques.
De même, un terme de type
∃x : X.P(x)
est le type de paires (x, p)
avec p : P(x)
, donc donné tout x
en X
nous pouvons construire une paire appropriée avec un membre P(x)
, ce qui donne |P(x)|
« choix ».
Par conséquent,
|∃x : X.P(x)| = Σ[x : X]|P(x)|
avec les mêmes mises en garde.
Cela justifie la notation commune pour les types dépendants dans les théories utilisant les symboles Π
et Σ
, en fait, de nombreuses théories brouillent la distinction entre `` pour tous '' et `` produit '' et entre `` il y a '' et `` somme '', en raison des correspondances susmentionnées.
Nous approchons!
Vecteurs: représentant des tuples dépendants
Pouvons-nous maintenant encoder des expressions numériques comme
Σ[n ∈ ℕ]Xⁿ
comme expressions de type?
Pas assez. Bien que nous puissions considérer de manière informelle la signification d'expressions comme Xⁿ
dans Haskell, où X
est un type et n
un nombre naturel, c'est un abus de notation; il s'agit d'une expression de type contenant un nombre: clairement pas une expression valide.
D'un autre côté, avec des types dépendants dans l'image, les types contenant des nombres sont précisément le point; en fait, les tuples ou «vecteurs» dépendants sont un exemple très souvent cité de la façon dont les types dépendants peuvent fournir une sécurité pragmatique au niveau du type pour des opérations telles que l'accès aux listes . Un vecteur n'est qu'une liste avec des informations au niveau du type concernant sa longueur: précisément ce que nous recherchons pour des expressions de type comme Xⁿ
.
Pendant la durée de cette réponse, laissez
Vec X n
être le type de longueur- n
vecteurs de X
valeurs de type.
Techniquement, n
il s'agit, plutôt que d'un nombre naturel réel , d'une représentation dans le système d'un nombre naturel. Nous pouvons représenter les nombres naturels ( Nat
) dans le style Peano comme zéro ( 0
) ou le successeur ( S
) d'un autre nombre naturel, et pour n ∈ ℕ
moi j'écris ˻n˼
pour signifier le terme dans Nat
lequel représente n
. Par exemple, ˻3˼
est S (S (S 0))
.
Ensuite nous avons
|Vec X ˻n˼| = |X|ⁿ
pour tout n ∈ ℕ
.
Types Nat: promotion de ℕ termes en types
Maintenant, nous pouvons encoder des expressions comme
Σ[n ∈ ℕ]Xⁿ
comme types. Cette expression particulière donnerait naissance à un type qui est bien sûr isomorphe au type de listes de X
, comme identifié dans la question. (Non seulement cela, mais d'un point de vue théorique de catégorie, la fonction de type - qui est un foncteur - prenant X
le type ci-dessus est naturellement isomorphe au foncteur List.)
Une dernière pièce du puzzle des fonctions «arbitraires» est de savoir comment coder, par
f : ℕ → ℕ
des expressions comme
Σ[n ∈ ℕ]f(n)Xⁿ
de sorte que nous pouvons appliquer des coefficients arbitraires à une série de puissance.
Nous comprenons déjà la correspondance des types algébriques avec les nombres, ce qui nous permet de mapper des types aux nombres et des fonctions de type aux fonctions numériques. On peut aussi aller dans l'autre sens! - en prenant un nombre naturel, il existe évidemment un type algébrique définissable avec autant de membres de terme, que nous ayons ou non des types dépendants. Nous pouvons facilement le prouver en dehors de la théorie des types par induction. Ce dont nous avons besoin, c'est d'un moyen de mapper des nombres naturels aux types, à l' intérieur du système.
Une réalisation agréable est que, une fois que nous avons des types dépendants, la preuve par induction et la construction par récursivité deviennent intimement similaires - en fait, c'est la même chose dans de nombreuses théories. Puisque nous pouvons prouver par induction qu'il existe des types qui répondent à nos besoins, ne devrions-nous pas pouvoir les construire?
Il existe plusieurs façons de représenter les types au niveau du terme. J'utiliserai ici une notation Haskellish imaginaire avec *
pour l'univers des types, lui-même généralement considéré comme un type dans un cadre dépendant. 1
De même, il existe au moins autant de façons de noter «l' ℕ
élimination» qu'il y a de théories de type dépendantes. J'utiliserai une notation de correspondance de motifs Haskellish.
Nous avons besoin d'une cartographie, α
de Nat
à *
, avec la propriété
∀n ∈ ℕ.|α ˻n˼| = n.
La pseudodéfinition suivante suffit.
data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe
α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)
On voit donc que l'action de α
reflète le comportement du successeur S
, ce qui en fait une sorte d'homomorphisme. Successor
est une fonction de type qui «ajoute un» au nombre de membres d'un type; c'est-à-dire |Successor a| = 1 + |a|
pour tous ceux a
dont la taille est définie.
Par exemple α ˻4˼
(qui est α (S (S (S (S 0))))
), est
Successor (Successor (Successor (Successor Zero)))
et les termes de ce type sont
Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))
nous donnant exactement quatre éléments: |α ˻4˼| = 4
.
De même, pour tout n ∈ ℕ
, nous avons
|α ˻n˼| = n
comme demandé.
- De nombreuses théories exigent que les membres de
*
soient de simples représentants de types, et une opération est fournie en tant que mappage explicite des termes de type *
à leurs types associés. D'autres théories permettent aux types littéraux eux-mêmes d'être des entités au niveau du terme.
Fonctions «arbitraires»?
Nous avons maintenant l'appareil pour exprimer une série de puissance entièrement générale sous forme de type!
Les séries
Σ[n ∈ ℕ]f(n)Xⁿ
devient le type
∃n : Nat.α (˻f˼ n) × (Vec X n)
où se ˻f˼ : Nat → Nat
trouve une représentation appropriée dans le langage de la fonction f
. Nous pouvons voir ceci comme suit.
|∃n : Nat.α (˻f˼ n) × (Vec X n)|
= Σ[n : Nat]|α (˻f˼ n) × (Vec X n)| (property of ∃ types)
= Σ[n ∈ ℕ]|α (˻f˼ ˻n˼) × (Vec X ˻n˼)| (switching Nat for ℕ)
= Σ[n ∈ ℕ]|α ˻f(n)˼ × (Vec X ˻n˼)| (applying ˻f˼ to ˻n˼)
= Σ[n ∈ ℕ]|α ˻f(n)˼||Vec X ˻n˼| (splitting product)
= Σ[n ∈ ℕ]f(n)|X|ⁿ (properties of α and Vec)
À quel point est-ce «arbitraire»? Nous sommes limités non seulement aux coefficients entiers par cette méthode, mais aux nombres naturels. En dehors de cela, f
peut être n'importe quoi, étant donné un langage Turing complet avec des types dépendants, nous pouvons représenter n'importe quelle fonction analytique avec des coefficients de nombres naturels.
Je n'ai pas étudié l'interaction de ceci avec, par exemple, le cas fourni dans la question de List X ≅ 1/(1 - X)
ou quel sens possible de tels 'types' négatifs et non entiers pourraient avoir dans ce contexte.
Espérons que cette réponse va dans une certaine mesure pour explorer jusqu'où nous pouvons aller avec des fonctions de type arbitraire.