Pracuję na małym kompilatorze rachunku lambda, który ma działający system wnioskowania typu Hindleya-Milnera, a teraz obsługuje także rekurencyjne let (nie w połączonym kodzie), co, jak rozumiem, powinno wystarczyć, aby Turing był kompletny .
Problem polega na tym, że nie mam pojęcia, jak to zrobić, aby obsługiwał listy lub czy już je obsługuje i po prostu muszę znaleźć sposób, aby je zakodować. Chciałbym móc je zdefiniować bez konieczności dodawania nowych reguł do systemu typów.
Najprostszym sposobem, w jaki mogę wymyślić listę, x
jest coś, co jest albo null
(albo pustą listą), albo parą zawierającą zarówno x
listę, jak i listę x
. Ale aby to zrobić, muszę być w stanie zdefiniować pary i lub, które moim zdaniem są produktem i typami sum.
Wydaje się, że mogę zdefiniować pary w ten sposób:
pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)
Ponieważ pair
miałby typ a -> (b -> ((a -> (b -> x)) -> x))
, po przejściu, powiedzmy, an int
i a string
, (int -> (string -> x)) -> x
dałby coś z typem , co byłoby reprezentacją pary int
i string
. Niepokoi mnie to, że jeśli reprezentuje parę, to dlaczego nie jest to logicznie równoważne ani nie sugeruje zdania int and string
? Jest to jednak równoważne (((int and string) -> x) -> x)
, jak gdybym mógł mieć tylko typy produktów jako parametry funkcji. Ta odpowiedźwydaje się, że rozwiązuje ten problem, ale nie mam pojęcia, co oznaczają symbole, których używa. Ponadto, jeśli tak naprawdę nie koduje typu produktu, czy jest coś, co mogę zrobić z typami produktów, których nie mogę zrobić z powyższą definicją par (biorąc pod uwagę, że mogę również zdefiniować n-krotki w ten sam sposób)? Jeśli nie, czy nie byłoby to sprzeczne z faktem, że nie można wyrazić koniunkcji (AFAIK) przy użyciu samej implikacji?
A może typ sumy? Czy mogę jakoś zakodować go przy użyciu tylko typu funkcji? Jeśli tak, czy wystarczyłoby to do zdefiniowania list? Albo czy jest jakiś inny sposób definiowania list bez konieczności rozszerzania mojego systemu typów? A jeśli nie, jakie zmiany musiałbym wprowadzić, jeśli chcę, aby było to tak proste, jak to możliwe?
Proszę pamiętać, że jestem programistą komputerowym, ale nie jestem informatykiem ani matematykiem i nieźle czytam notację matematyczną.
Edycja: Nie jestem pewien, jaka jest nazwa techniczna tego, co do tej pory zaimplementowałem, ale wszystko, co mam, to w zasadzie kod, który podłączyłem powyżej, który jest algorytmem generującym ograniczenia, który wykorzystuje reguły dla aplikacji, pobranych abstrakcji i zmiennych z algorytmu Hinleya-Milnera, a następnie algorytmu unifikacji, który otrzymuje typ główny. Na przykład wyrażenie \a.a
zwróci typ a -> a
, a wyrażenie \a.(a a)
wyrzuci błąd sprawdzania. Na dodatek nie istnieje dokładnie let
reguła, ale funkcja, która wydaje się mieć ten sam efekt, który pozwala zdefiniować rekurencyjne funkcje globalne, takie jak ten pseudo-kod:
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.
}
Kod zasadniczo pobiera typ terminu, jak zwykle, ale przed ujednoliceniem dodaje do definicji zakresu nazwę funkcji zdefiniowanej za pomocą typu fikcyjnego, dzięki czemu można z niej korzystać rekurencyjnie.
Edycja 2: Właśnie zdałem sobie sprawę, że potrzebuję również typów rekurencyjnych, których nie mam, aby zdefiniować listę tak, jak chcę.
let func = \x -> (func x)
) Dostaniesz to, co mam.