Domanda

Ho pensato ad alcuni concetti alla base di una nuova lingua.All'inizio era una specie di giocattolo, ma ora mi chiedo se possa davvero significare qualcosa.Sto pubblicando questa domanda su Stack Overflow per vedere se è già stata fatta in precedenza e se posso ottenere feedback, idee o altre informazioni.

Ho iniziato a pensarci soprattutto dopo aver letto Presentazione di Jonathan Edward sulla programmazione dichiarativa.L'ho poi mescolato con alcune delle mie idee più vecchie e con ciò che ho visto nei linguaggi moderni.

L'idea principale dietro la programmazione dichiarativa è "cosa" vs."Come." Tuttavia, l'ho sentito così tante volte, quindi sembra quasi sempre essere come la parola "interessante", dove in realtà non ti dice nulla, il che è frustrante.

Nella versione di Jonathan Edward, però, egli comincia innanzitutto con l'enfatizzare valutazione pigra.Ciò ha alcune conseguenze interessanti, vale a dire programmazione reattiva funzionale (FRP).Ecco un esempio di FRP con animazione (usando una sintassi che ho inventato):

x as time * 2 // time is some value representing the current time
y as x + (2 * 500)

new Point(x, y)

Quindi qui i valori cambiano automaticamente se cambiano gli input.In una delle mie lingue preferite, D, si distingueva tra funzioni "pure" e "impure".Una funzione pura è una funzione che non aveva alcun collegamento con il mondo esterno e utilizzava solo altre funzioni pure.Altrimenti sarebbe impuro.Il punto era che ci si poteva sempre fidare che una funzione pura restituisse lo stesso valore per determinati argomenti.

Suppongo che qui si applichi un principio transitivo simile.La nostra impurità è time.Tutto toccato time, essendo x, così y, e quindi new Point(x, y) sono impuri.Tuttavia, attenzione (2 * 500) è puro.Quindi vedi che questo dice al compilatore dove sono i suoi limiti.Lo considero come semplificare un'espressione matematica con variabili:

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

Dicendo al compilatore cosa è puro e cosa non lo è, possiamo semplificare molto i nostri programmi.Un altro punto sono i dati desiderosi o mutevoli.Jonathan Edward ha riconosciuto l'input come mutevole e desideroso, ma l'output come funzionale e pigro.Fondamentalmente, dato un nuovo input, il programma ha definito un cambiamento di stato atomico, poi l'output sarebbe semplicemente una funzione dello stato corrente.Se vuoi capire perché questo può essere importante, guarda la presentazione.L'input è impuro.La valutazione pigra aiuta a definire il cambiamento dello stato atomico.Diamo un'occhiata a come un programma verrebbe scritto proceduralmente:

void main ()
{
    String input = "";

    writeln("Hello, world!");
    writeln("What's your name? ");

    input = readln();

    writeln("Hello, %s!", input);
    writeln("What's your friends name? ");

    input = readln();

    writeln("Hello to you too, %s!", input);
}

Ecco il bind La parola chiave dice che il seguente codice viene eseguito se begin i cambiamenti.IL mutable La parola chiave dice che l'input non è pigro, ma desideroso.Ora diamo un'occhiata a come un "cambiamento di stato atomico" potrebbe rappresentarlo.

program:
    mutable step := 0

    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")
        ++step

    bind readln() as input when step = 1:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        ++step

    bind readln() as input when step = 2:
        writeln("Hello to you too, %s!", input)

Ora qui vediamo qualcosa che potrebbe essere reso più semplice e più leggibile per il programmatore.Prima di tutto è il brutto step variabile e come dobbiamo incrementarla e testarla ogni volta.Ecco un esempio di come potrebbe apparire una versione nuova e migliorata:

program:
    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")

    bind readln() as input:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        yield // This just means the program jumps to here instead of at the beginning
        writeln("Hello to you too, %s!", input)
        halt

Così va meglio.Non perfetto, però.Ma se conoscessi la risposta perfetta, non sarei qui, giusto?

Ecco un esempio migliore, utilizzando un motore di gioco:

class VideoManager:
    bind begin: // Basically a static constructor, will only be called once and at the beginning
        // Some video set up stuff

    bind end: // Basically a static destructor
        // Some video shut down stuff

class Input:
    quitEvent     as handle // A handle is an empty value, but can be updated so code that's bound to it changes.
    keyboardEvent as handle(KeyboardEvent) // This handle does return a value though
    mouseEvent    as handle(MouseEvent)

    // Some other code manages actually updating the handles.

class Sprite:
    mutable x := 0
    mutable y := 0

    bind this.videoManager.updateFrame:
        // Draw this sprite

class FieldState:
    input  as new Input
    player as new Sprite

    bind input.quitEvent:
        halt

    bind input.keyboardEvent as e:
        if e.type = LEFT:
            this.player.x -= 2
        else if e.type = RIGHT:
            this.player.x += 2
        else if e.type = UP:
            this.player.y -= 2
        else if e.type = DOWN:
            this.player.y += 2

Mi piace il fatto che questo non richieda callback, eventi o addirittura loop o altro, e i thread siano ovvi.È più facile capire cosa sta succedendo e non è solo la sintassi simile a Python.Penso che sia il genere di cose come quando gli sviluppatori del linguaggio si sono resi conto che c'erano solo poche cose per cui le persone usavano etichette e goto:rami e cicli condizionali.Così hanno costruito if-then-else, while, e for nei linguaggi, le etichette e i goto sono diventati deprecati, e i compilatori così come le persone potevano dire cosa stava succedendo.La maggior parte di ciò che utilizziamo deriva da questo processo.

Tornando ai thread, la cosa bella è che i thread sono molto più flessibili.Se il compilatore è libero di fare ciò che vuole perché ci siamo avvicinati a dire ciò che vogliamo, non come vogliamo che sia fatto.Pertanto, il compilatore può trarre vantaggio dai processori multi-core e distribuiti, compensando comunque le piattaforme senza un buon supporto per il threading.

C'è un'ultima cosa che vorrei menzionare.E questa è la mia opinione sui modelli.Si trattava di una specie di uovo concettuale che ha iniziato a svilupparsi quando ho iniziato a programmare (circa 2 anni fa, in realtà), per poi iniziare ad aprirsi.Fondamentalmente si trattava del principio di astrazione, ma si estendeva oltre le classi e gli oggetti.

Aveva a che fare con il modo in cui percepivo una funzione.Per esempio:

int add (int a, int b)
{
    return a + b;
}

Va bene, add restituito un int, ma cosa era Esso?Sembrava un po' come un int in attesa che accada.Come un puzzle senza pochi pezzi.C'erano possibilità limitate e solo alcuni pezzi si adattavano, ma una volta finito avevi un prodotto finito che potevi utilizzare altrove.Questo è, come ho detto, il principio di astrazione.Ecco alcuni esempi di ciò che penso siano astrazione + pezzi mancanti -> relazioni concrete:

  • funzione + argomenti -> valore
  • classe astratta + metodi -> classe
  • classe + valori di istanza -> oggetto
  • modello + argomenti -> funzione o classe
  • programma + input + stato -> output

Sono tutti strettamente imparentati.Sembra che se ne possa approfittare.Ma come?Ancora una volta, ecco perché questa è una domanda.Ma la valutazione pigra è interessante qui, poiché puoi passare qualcosa con i suoi pezzi ancora mancanti a qualcos'altro.Per il compilatore si tratta principalmente di dereferenziare i nomi fino alle impurità.Come il mio esempio dall'alto:

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

Più pezzi dai al compilatore, più potrà finirlo e ridurre il programma al suo nucleo essenziale.E il add la funzione sopra verrebbe risolta in fase di compilazione, automaticamente, poiché non si basa su risorse esterne.Potrebbero essere risolti anche molte classi e oggetti, nonché enormi porzioni di programmi, a seconda di quanto sia intelligente il compilatore.

È tutto per ora.Se hai visto esempi di queste cose già fatte, mi piacerebbe vedere.E se hai idee, innovazioni, risorse o feedback, lo apprezzerei anche io.

È stato utile?

Soluzione

Vorresti sicuramente dare un'occhiata a Haskell linguaggio di programmazione.

Haskell è estremamente dichiarativo, la valutazione pigra è integrata ed esistono persino librerie di programmazione reattiva funzionale.Ma soprattutto, Haskell lo è puramente funzionale, cioè.tutto, proprio tutto, lo è puro.

Quindi la domanda è: come fa Haskell a gestire le impurità necessarie che emergono da qualsiasi IO.

La risposta si adatta abbastanza bene ai pensieri che hai presentato.Haskell utilizza un costrutto matematico chiamato monadi che rappresentano fondamentalmente un calcolo che produce un valore insieme a una funzione bind (>>= come operatore infisso), che sequenzia tali calcoli.

Quindi prendiamo qualche esempio di IO:Leggi una riga e digita il tuo nome...Anche l'IO è puro, quindi non puoi semplicemente eseguire qualcosa.Invece, crei un calcolo IO maggiore

do
    putStr "Enter your name: "
    name <- getLine
    putStrLn ("Hello " ++ name)

Sembra piuttosto imperativo, ma sotto il cofano è solo sintassi per

(putStr "Enter your name: ") >>
(getLine >>= \name ->
 putStrLn ("Hello " ++ name))

Ora puoi definirlo bind/>>= per tipi arbitrari di calcoli in qualsiasi modo tu voglia.Quindi in effetti tutto ciò di cui hai parlato può essere implementato in questo modo, anche FRP.

Prova a cercare monadi o Haskell qui su Stackoverflow;ci sono state molte domande su questo argomento.E dopo tutto, è ancora tutto controllato dal tipo e quindi la correttezza può essere applicata dal compilatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top