Représentation de la concaténation au niveau du type

8

J'aimerais en savoir plus sur la programmation concaténative à travers la création d'un petit langage simple, basé sur la pile et suivant le paradigme concaténatif.

Malheureusement, je n'ai pas trouvé beaucoup de ressources concernant les langages concaténatifs et leur mise en œuvre, alors excusez-moi à l'avance pour ma possible naïveté.

J'ai donc défini mon langage comme une simple séquence de concaténation de fonctions, représentée dans l'AST sous forme de liste:

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

data Literal
    = Int Int
    | Float Float

data LiteralOperation
    = Add | Sub | Mul | Div

Le programme suivant, 4 2 swap dup * +(correspondant à 2 * 2 + 4) une fois analysé, donnera l'AST suivant:

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

Maintenant, je dois déduire et vérifier les types.

J'ai écrit ce système de type:

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

C'est là que ma question entre en jeu, car je ne sais pas quel type déduire de cette expression. Le type qui en résulte est évident, Intmais je ne sais pas comment vérifier entièrement ce programme au niveau du type.

Au début, comme vous pouvez le voir ci-dessus, j'avais pensé à un TConctype qui représente la concaténation de la même manière que le TFuntype représente une fonction, car à la fin la séquence de concaténation forme une fonction unique.

Une autre option, que je n'ai pas encore explorée, serait d'appliquer la règle d'inférence de composition de fonction à chaque élément de cette séquence d'expression. Je ne sais pas comment cela fonctionnerait avec la pile.

La question est la suivante: comment procéder? Quel algorithme utiliser et quelle approche au niveau du type devrait être préférée?

Rusé
la source

Réponses:

9

Une idée majeure des langages concaténatifs est que la syntaxe et le domaine sémantique forment des monoïdes et que la sémantique est un homomorphisme monoïde . La syntaxe est le monoïde libre généré par les opérations de base, mieux connu sous le nom de liste. Son fonctionnement est une concaténation de liste, c'est- (++)à- dire dans Haskell. Dans le contexte non typé, le domaine sémantique n'est que le monoïde des endofonctions (sur piles) avec la composition comme opération. En d'autres termes, un interprète devrait ressembler à ceci:

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 []

Faire un compilateur ( très naïf) est tout aussi simple. La seule chose qui change est le monoïde cible qui sera désormais un monoïde syntaxique construit à partir d'un fragment de la syntaxe de la langue cible ainsi interpretOpdeviendra compileOp. Ce monoïde cible peut être des séquences d'instructions avec l'opération de composition séquentielle, c'est-à-dire ;. Vous pouvez cependant être beaucoup plus sophistiqué .

Les systèmes de types pour les langues concaténatives ne sont pas aussi évidents, et il n'y a presque pas de langues concaténatives typées. Cat est l'exemple le plus significatif que je connaisse. Une façon de commencer à l'aborder et à découvrir certains des problèmes qui se posent est d'incorporer un langage concaténatif dans Haskell. Vous découvrez rapidement que vous ne voulez pas add :: (Int, Int) -> Intcar cela ne composera pas. Au lieu de cela, vous l'avez fait add :: (Int, (Int, s)) -> (Int, s). Cela fonctionne extrêmement bien pour les choses simples. Il s'agit également de types de rangs de l'homme relativement clairement pauvres. L'un des premiers obstacles les plus importants que vous rencontrerez est celui des citations. Le problème est que cela [add]devrait correspondre à quelque chose avec un type comme s -> ((forall s'. (Int, (Int, s')) -> (Int, s')), s)qui nécessite des types de rang supérieur et une instanciation imprédicative. Le chat semble avoir les deux. Il a certainement des types mieux classés et il remplacera un polytype par une variable de type. Il peut s'agir de faire les choses d'une manière qui peut être comprise sans imprédicativité. Accomplir cela avec une intégration dans Haskell pourrait être faisable en utilisant des listes de types, des familles de types (fermées) et une quantification universelle locale. À ce stade, cependant, la création d'un système de type personnalisé est probablement plus logique.

Les opérations avec des effets de pile non uniformes sont également susceptibles d'être problématiques, mais, dans la plupart des cas, il est logique de simplement les omettre et de fournir d'autres moyens de faire des choses qui garantissent une pile cohérente.

Derek Elkins a quitté SE
la source