Rappresentazione della concatenazione a livello di tipo


8

Vorrei saperne di più sulla programmazione concatenativa attraverso la creazione di un piccolo linguaggio semplice, basato sullo stack e seguendo il paradigma concatenativo.

Sfortunatamente, non ho trovato molte risorse relative ai linguaggi concatenativi e alla loro implementazione, quindi scusami in anticipo per la mia possibile ingenuità.

Ho quindi definito il mio linguaggio come una semplice sequenza di concatenazione di funzioni, rappresentata nell'AST come un elenco:

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

data Literal
    = Int Int
    | Float Float

data LiteralOperation
    = Add | Sub | Mul | Div

Il seguente programma, 4 2 swap dup * +(corrispondente a 2 * 2 + 4) una volta analizzato, fornirà il seguente AST:

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

Ora devo dedurre e controllare i tipi.

Ho scritto questo tipo di sistema:

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

È qui che arriva la mia domanda, perché non so quale tipo dedurre da quell'espressione. Il tipo risultante è ovvio, lo è Int, ma non so come controllare completamente questo programma a livello di tipo.

All'inizio, come puoi vedere sopra, avevo pensato a un TConctipo che rappresenta la concatenazione allo stesso modo in cui il TFuntipo rappresenta una funzione, perché alla fine la sequenza di concatenazione forma una funzione unica.

Un'altra opzione, che non ho ancora esplorato, sarebbe quella di applicare la regola di inferenza della composizione della funzione a ciascun elemento di questa sequenza di espressioni. Non so come funzionerebbe con lo stack-based.

La domanda è così: come lo facciamo? Quale algoritmo da usare e quale approccio a livello di tipo dovrebbe essere preferito?

Risposte:


9

Un'idea principale dei linguaggi concatenativi è che la sintassi e il dominio semantico formano i monoidi e la semantica è un omomorfismo monoide . La sintassi è il monoide libero generato dalle operazioni di base, meglio noto come elenco. La sua operazione è la concatenazione di elenchi, ovvero (++)in Haskell. Nel contesto non tipizzato, il dominio semantico è solo il monoide di endofunzioni (su stack) con composizione come operazione. In altre parole, un interprete dovrebbe essere simile al seguente:

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

Creare un compilatore ( molto ingenuo) è altrettanto semplice. L'unica cosa che cambia è il monoide target che ora diventerà un monoide sintattico costruito da un frammento della sintassi della lingua target così interpretOpdiventerà compileOp. Questo monoide bersaglio può essere una sequenza di affermazioni con l'operazione di composizione sequenziale, cioè ;. Puoi essere molto più sofisticato però .

I sistemi di tipi per i linguaggi concatenativi non sono così ovvi e quasi non esistono linguaggi concatenativi tipizzati. Cat è l'esempio più significativo di cui sono a conoscenza. Un modo per iniziare ad affrontarlo e sperimentare alcuni dei problemi che emergono è quello di incorporare un linguaggio concatenativo in Haskell. Scoprirai rapidamente che non vuoi in add :: (Int, Int) -> Intquanto questo non si comporrà. Invece hai add :: (Int, (Int, s)) -> (Int, s). Funziona molto bene per cose semplici. Questo è anche relativamente chiaramente i tipi di fila dei poveri. Uno dei primi e più significativi ostacoli che colpirai è la gestione delle quotazioni. Il problema è che [add]dovrebbe corrispondere a qualcosa con un tipo come s -> ((forall s'. (Int, (Int, s')) -> (Int, s')), s)che richiede tipi di rango superiore e istanziazione impredicativa. Il gatto sembra avere entrambi. Ha certamente tipi classificati più alti e sostituirà un politipo con una variabile di tipo. Potrebbe fare le cose in un modo che può essere compreso senza impredicibilità. Realizzare questo con un incorporamento in Haskell potrebbe essere fattibile usando elenchi a livello di tipo, famiglie di tipi (chiuse) e quantificazione universale locale. A questo punto, tuttavia, rendere probabilmente più logico un sistema di tipo personalizzato.

Anche le operazioni con effetti di stack non uniformi possono essere problematiche, ma, nella maggior parte dei casi, ha senso ometterle e fornire mezzi alternativi per fare cose che garantiscano uno stack coerente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.