Darstellung der Verkettung auf Typebene

8

Ich möchte mehr über die verkettete Programmierung erfahren, indem ich eine kleine einfache Sprache erstelle, die auf dem Stapel basiert und dem verketteten Paradigma folgt.

Leider habe ich nicht viele Ressourcen zu verketteten Sprachen und deren Implementierung gefunden. Entschuldigen Sie mich daher im Voraus für meine mögliche Naivität.

Ich habe meine Sprache daher als eine einfache Folge der Verkettung von Funktionen definiert, die im AST als Liste dargestellt wird:

data Operation
    = Concat [Operation]
    | Quotation Operation
    | Var String
    | Lit Literal
    | LitOp LiteralOperation

data Literal
    = Int Int
    | Float Float

data LiteralOperation
    = Add | Sub | Mul | Div

Das folgende Programm 4 2 swap dup * +(entspricht 2 * 2 + 4), sobald es analysiert wurde, gibt den folgenden AST aus:

Concat [Lit (Int 4), Lit (Int 2), Var "swap", Var "dup", LitOp Mul, LitOp Add]

Jetzt muss ich die Typen ableiten und überprüfen.

Ich habe dieses Typsystem geschrieben:

data Type
    = TBasic BasicType   -- 'Int' or 'Float'
    | TVar String        -- Variable type
    | TQuoteE String     -- Empty stack, noted 'A'
    | TQuote String Type -- Non empty stack, noted 'A t'
    | TConc Type Type    -- A type for the concatenation
    | TFun Type Type     -- The type of functions

Hier kommt meine Frage ins Spiel, weil ich nicht weiß, welchen Typ ich aus diesem Ausdruck ableiten soll. Der resultierende Typ ist offensichtlich, Intaber ich weiß nicht, wie ich dieses Programm tatsächlich vollständig auf Typebene überprüfen soll.

Wie Sie oben sehen können, hatte ich am Anfang an einen TConcTyp gedacht, der die Verkettung genauso darstellt wie der TFunTyp eine Funktion, da die Verkettungssequenz am Ende eine eindeutige Funktion bildet.

Eine andere Option, die ich noch nicht untersucht habe, wäre, die Inferenzregel der Funktionszusammensetzung auf jedes Element dieser Ausdruckssequenz anzuwenden. Ich weiß nicht, wie es mit dem Stack-basierten funktionieren würde.

Die Frage ist also: Wie machen wir das? Welcher Algorithmus ist zu verwenden und welcher Ansatz auf Typebene sollte bevorzugt werden?

Foxy
quelle

Antworten:

9

Eine Hauptidee von verketteten Sprachen ist, dass die Syntax und die semantische Domäne Monoide bilden und die Semantik ein monoider Homomorphismus ist . Die Syntax ist das freie Monoid, das durch die Grundoperationen erzeugt wird, besser bekannt als Liste. Die Operation ist die Listenverkettung, dh (++)in Haskell. Im untypisierten Kontext ist die semantische Domäne nur das Monoid von Endfunktionen (auf Stapeln) mit der Zusammensetzung als Operation. Mit anderen Worten, ein Dolmetscher sollte wie folgt aussehen:

data Op = PushInt Int| Call Name | Quote Code | Add | ... -- etc.
type Code = [Op]

-- Run-time values
data Value = Q (Endo Stack) | I Int | ... -- etc.
type Stack = [Value]

-- You'd probably add an environment of type Map Name (Endo Stack)
interpretOp :: Op -> Endo Stack
interpretOp (PushInt n) = Endo (I n:)
interpretOp (Quote c) = Endo (Q (interpetCode c):)
interpretOp op = ... -- etc.

interpretCode :: Code -> Endo Stack
interpretCode = foldMap interpretOp

runCode :: Code -> Stack
runCode code = case interpretCode code of Endo f -> f []

Einen ( sehr naiven) Compiler zu erstellen ist genauso einfach. Das einzige, was sich ändert, ist das Zielmonoid, das nun ein syntaktisches Monoid sein wird, das aus einem Fragment der Syntax der Zielsprache aufgebaut interpretOpwird compileOp. Dieses Ziel kann monoid Sequenzen von Anweisungen , die mit dem Betrieb der sequentiellen Zusammensetzung, dh sein ;. Sie können jedoch viel anspruchsvoller sein .

Typsysteme für verkettete Sprachen sind nicht so offensichtlich, und es gibt fast keine typisierten verketteten Sprachen. Katze ist das bedeutendste Beispiel, das mir bekannt ist. Eine Möglichkeit, sich dem Problem zu nähern und einige der auftretenden Probleme zu lösen, besteht darin, eine verkettete Sprache in Haskell einzubetten. Sie stellen schnell fest, dass Sie nicht wollen, add :: (Int, Int) -> Intda dies nicht komponiert. Stattdessen hast du add :: (Int, (Int, s)) -> (Int, s). Dies funktioniert sehr gut für einfache Dinge. Dies sind auch relativ eindeutig die Zeilentypen armer Männer. Eine der ersten und wichtigsten Hürden, mit denen Sie konfrontiert werden, ist der Umgang mit Zitaten. Das Problem ist, dass dies [add]einem Typ entsprechen sollte, s -> ((forall s'. (Int, (Int, s')) -> (Int, s')), s)der höherrangige Typen und eine sofortige Instanziierung erfordert. Katze scheint beides zu haben. Es hat sicherlich höherrangige Typen und ersetzt eine Typvariable durch einen Polytyp. Es kann Dinge auf eine Weise tun, die ohne Impredikativität verstanden werden kann. Dies mit einer Einbettung in Haskell zu erreichen, kann mithilfe von Listen auf Typebene, (geschlossenen) Typfamilien und lokaler universeller Quantifizierung möglich sein. An diesem Punkt ist es jedoch wahrscheinlich sinnvoller, ein benutzerdefiniertes Typsystem zu erstellen.

Operationen mit ungleichmäßigen Stapeleffekten sind wahrscheinlich ebenfalls problematisch. In den meisten Fällen ist es jedoch sinnvoll, sie einfach wegzulassen und alternative Mittel bereitzustellen, um einen konsistenten Stapel zu gewährleisten.

Derek Elkins verließ SE
quelle