Je travaille sur un petit compilateur de calcul lambda qui a un système d'inférence de type Hindley-Milner qui fonctionne et prend désormais également en charge le let récursif (pas dans le code lié), ce qui, je crois, devrait suffire à le rendre complet .
Le problème est maintenant que je n'ai aucune idée de comment faire pour qu'il prenne en charge les listes, ou s'il les supporte déjà et j'ai juste besoin de trouver un moyen de les encoder. Je voudrais pouvoir les définir sans avoir à ajouter de nouvelles règles au système de type.
La façon la plus simple de penser à une liste x
est comme quelque chose qui est null
(ou la liste vide), ou une paire qui contient à la fois un x
et une liste de x
. Mais pour ce faire, je dois être en mesure de définir des paires et / ou, qui sont, je crois, le type de produit et de somme.
Semble que je peux définir des paires de cette façon:
pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)
Puisque pair
aurait le type a -> (b -> ((a -> (b -> x)) -> x))
, après avoir passé, disons, un int
et un string
, cela donnerait quelque chose avec le type (int -> (string -> x)) -> x
, qui serait la représentation d'une paire de int
et string
. Ce qui me dérange ici, c'est que si cela représente une paire, pourquoi cela n'est-il pas logiquement équivalent à, ni n'implique la proposition int and string
? Cependant, est équivalent à (((int and string) -> x) -> x)
, comme si je ne pouvais avoir que des types de produits comme paramètres de fonctions. Cette réponsesemblent résoudre ce problème, mais je n'ai aucune idée de ce que signifient les symboles qu'il utilise. De plus, si cela n'encode pas vraiment un type de produit, puis-je faire quelque chose avec les types de produits que je ne pourrais pas faire avec ma définition des paires ci-dessus (étant donné que je peux également définir des n-tuples de la même manière)? Sinon, cela ne contredirait-il pas le fait que vous ne pouvez pas exprimer la conjonction (AFAIK) en utilisant uniquement l'implication?
Et le type de somme? Puis-je en quelque sorte l'encoder en utilisant uniquement le type de fonction? Si oui, cela suffirait-il pour définir des listes? Ou bien, existe-t-il un autre moyen de définir des listes sans avoir à étendre mon système de type? Et sinon, quels changements devrais-je apporter si je veux que ce soit aussi simple que possible?
Veuillez garder à l'esprit que je suis un programmeur informatique, mais pas un informaticien ni un mathématicien et assez mauvais en lecture de notation mathématique.
Edit: je ne sais pas quel est le nom technique de ce que j'ai mis en œuvre jusqu'à présent, mais tout ce que j'ai est essentiellement le code que j'ai lié ci-dessus, qui est un algorithme de génération de contraintes qui utilise les règles pour les applications, les abstractions et les variables prises à partir de l'algorithme de Hinley-Milner, puis un algorithme d'unification qui obtient le type principal. Par exemple, l'expression \a.a
donnera le type a -> a
et l'expression \a.(a a)
lèvera une erreur de vérification se produit. En plus de cela, il n'y a pas exactement une let
règle mais une fonction qui semble avoir le même effet qui vous permet de définir des fonctions globales récursives comme ce pseudo-code:
GetTypeOfGlobalFunction(term, globalScope, nameOfFunction)
{
// Here 'globalScope' contains a list of name-value pair where every value is of class 'ClosedType',
// meaning their type will be cloned before unified in the unification algorithm so that they can be used polymorphically
tempType = new TypeVariable() // Assign a dummy type to `tempType`, say, type 'x'.
// The next line creates an scope with everything in 'globalScope' plus the 'nameOfFunction = tempType' name-value pair
tempScope = new Scope(globalScope, nameOfFunction, tempType)
type = TypeOfTerm(term, tempScope) // Calculate the type of the term
Unify(tempType, type)
return type
// After returning, the code outside will create a 'ClosedType' using the returned type and add it to the global scope.
}
Le code obtient essentiellement le type du terme comme d'habitude, mais avant d'unifier, il ajoute le nom de la fonction en cours de définition avec un type factice dans la portée du type afin qu'il puisse être utilisé de l'intérieur de manière récursive.
Edit 2: Je viens de réaliser que j'avais également besoin de types récursifs, que je n'ai pas, pour définir une liste comme je le veux.
let func = \x -> (func x)
) vous obtenez ce que j'ai.