3

Ich habe über einige Konzepte nachgedacht, die einer neuen Sprache zugrunde liegen. Es war zunächst ein Spielzeug, aber jetzt frage ich mich, ob es wirklich etwas bedeuten könnte. Ich schreibe diese Frage an Stack Overflow, um zu sehen, ob es schon einmal gemacht wurde und ob ich Feedback, Ideen oder andere Informationen erhalten kann.Feedback, Ressourcen und Informationen für deklarative Programmiersprache

Ich begann darüber nachzudenken, vor allem nach dem Lesen Jonathan Edward's presentation on declarative programming. Ich mischte es dann mit einigen meiner älteren Ideen und was ich in modernen Sprachen gesehen habe.

Die Grundidee der deklarativen Programmierung ist "Was" und "Wie". Allerdings habe ich das schon so oft gehört, so scheint es fast immer wie das Wort "interessant" zu sein, wo es dir eigentlich nichts sagt, was frustrierend ist.

In Jonathan Edwards Version begann er zuerst mit Betonung lazy evaluation. Dies hat einige interessante Konsequenzen, nämlich functional reactive programming (FRP). Hier ist ein Beispiel von FRP mit Animation (unter Verwendung einer Syntax I aus):

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

new Point(x, y) 

Also hier die Werte nur automatisch ändern, wenn die Eingänge ändern. In einer meiner Lieblingssprachen, D, wurde zwischen "reinen" und "unreinen" Funktionen unterschieden. Eine reine Funktion ist eine Funktion, die keine Verbindung zur Außenwelt hat und nur andere reine Funktionen verwendet. Sonst wäre es unrein. Der Punkt war, dass Sie immer einer reinen Funktion vertrauen konnten, um denselben Wert für gegebene Argumente zurückzugeben.

Ich denke, ein ähnliches transitives Prinzip gilt hier. Unsere Verunreinigung ist time. Alles, was durch time berührt wird, ist x, also y und somit new Point(x, y) sind unrein. Beachten Sie jedoch (2 * 500) ist rein. Sie sehen also, dass der Compiler seine Grenzen angibt. Ich denke, es wie einen mathematischen Ausdruck mit Variablen vereinfachen:

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

durch den Compiler zu sagen, was rein ist und was nicht, wir unsere Programme viel vereinfachen kann. Ein weiterer Punkt sind eifrige oder veränderbare Daten. Jonathan Edwards erkannte die Eingabe als veränderlich und eifrig, aber die Ausgabe als funktional und faul. Grundsätzlich, gegeben neue Eingabe, das Programm definiert einen atomaren Statuswechsel, und dann der Ausgang wäre einfach eine Funktion des aktuellen Zustands. Wenn Sie sehen möchten, warum dies wichtig sein kann, sehen Sie sich die Präsentation an. Eingabe ist unrein. Lazy Evaluation hilft bei der Definition der atomaren Zustandsänderung. Werfen wir einen Blick darauf werfen, wie ein Programm würde prozedural geschrieben werden:

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); 
} 

Hier wird das bind Schlüsselwort sagt, dass der folgende Code, wenn begin Änderungen ausgeführt wird. Das Schlüsselwort mutable besagt, dass die Eingabe nicht faul, sondern eifrig ist. Sehen wir uns jetzt an, wie eine "atomare Zustandsänderung" es darstellen könnte.

Jetzt hier sehen wir etwas, das für den Programmierer einfacher und lesbarer gemacht werden könnte. Vor allem ist die hässliche step Variable und wie wir es jedes Mal erhöhen und testen müssen. Hier ist ein Beispiel, wie eine neue und verbesserte Version aussehen könnte:

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 

Das ist besser. Aber nicht perfekt. Aber wenn ich die perfekte Antwort wüsste, wäre ich nicht hier, oder?

Hier ist ein besseres Beispiel, ein Spiel-Engine:

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 

Ich mag, dass dies nicht Rückrufe benötigt, Ereignisse oder sogar Schleifen oder irgendetwas, und Fäden liegen auf der Hand. Es ist einfacher zu sagen, was passiert, und es ist nicht nur die Python-artige Syntax. Ich denke, es ist eine Sache wie wenn Sprachentwickler feststellen, dass es nur ein paar Dinge gibt, bei denen Leute Labels und GOTOs verwenden: bedingte Verzweigungen und Schleifen. Also bauten sie if-then-else, während und in Sprachen, Labels und Goto's veraltet wurden, und sowohl die Compiler als auch die Leute konnten erzählen, was vor sich ging. Das meiste von dem, was wir verwenden, kommt von diesem Prozess.

Zurück zu Threads, das Schöne daran ist, dass Threads viel flexibler sind. Wenn der Compiler frei ist, zu tun, was er will, weil wir näher gekommen sind zu sagen, was wir wollen, nicht wie wir es wollen. Daher kann der Compiler die Vorteile von Multi-Core- und verteilten Prozessoren nutzen und dennoch Plattformen ohne gute Threading-Unterstützung kompensieren.

Es gibt eine letzte Sache, die ich erwähnen möchte. Und das ist meine Sicht auf Vorlagen. Das war eine Art konzeptionelles Ei, das sich zu entwickeln begann, als ich anfing zu programmieren (vor ungefähr 2 Jahren), und später begann, sich zu öffnen. Im Grunde war es das Prinzip der Abstraktion, aber es erstreckte sich weiter als Klassen und Objekte.

Es hatte damit zu tun, wie ich eine Funktion wahrnahm. Zum Beispiel:

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

Okay, kehrte add eine int, aber was war es? Es fühlte sich an wie ein int Warten auf passieren. Wie ein Puzzle ohne ein paar Stücke. Es gab begrenzte Möglichkeiten und nur bestimmte Teile passten, aber wenn du fertig warst, hattest du ein fertiges Produkt, das du dann woanders verwenden kannst. Das ist, wie gesagt, das Prinzip der Abstraktion. Hier sind einige Beispiele von dem, was ich denke, sind Abstraktion + fehlende Teile -> konkrete Beziehungen:

  • Funktion + Argumente -> Wert
  • abstrakte Klasse + Methoden -> Klasse
  • Klasse + Instanzwerte -> Objekt
  • Vorlage + Argumente -> Funktion oder Klasse
  • Programm + Eingang + Zustand -> Ausgang

Sie sind alle eng miteinander verbunden. Es scheint, dass dies ausgenutzt werden könnte. Aber wie? Auch deshalb ist das eine Frage. Aber faule Bewertung ist hier interessant, da man etwas mit seinen noch fehlenden Teilen an etwas anderes weitergeben kann. Für den Compiler ist es meist eine Frage der Dereferenzierung von Namen bis hin zu den Unreinheiten. Wie mein Beispiel von oben:

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

Je mehr Teile man den Compiler geben, desto mehr kann es beenden und das Programm zu seinen wesentlichen Kern reduzieren. Und die obige add-Funktion wird automatisch zur Kompilierzeit aufgelöst, da sie auf keine externen Ressourcen angewiesen ist. Sogar viele Klassen und Objekte könnten aufgelöst werden, und große Teile von Programmen, abhängig davon, wie schlau der Compiler ist.

Das ist alles für jetzt. Wenn Sie Beispiele für diese Dinge schon gesehen haben, würde ich gerne sehen.Und wenn Sie Ideen, Innovationen, Ressourcen oder Feedback haben, würde ich das ebenfalls begrüßen.

Antwort

2

Sie möchten auf jeden Fall die Programmiersprache Haskell ansehen.

Haskell ist extrem deklarativ, Lazy-Evaluation ist eingebaut und sogar funktionale reaktive Programmierbibliotheken existieren. Aber vor allem ist Haskell rein funktional, d.h. alles, wirklich alles, ist pure.

Die Frage ist also, wie geht Haskell mit den notwendigen Verunreinigungen um, die durch irgendein IO entstehen.

Die Antwort passt gut zu den Gedanken, die Sie vorgestellt haben. Haskell verwendet ein mathematisches Konstrukt namens Monaden, die im Grunde eine Berechnung darstellen, die zusammen mit einer Funktion bind (als Infix-Operator) einen Wert erzeugt, der solche Berechnungen abarbeitet.

Also nehmen wir ein IO Beispiel: Lesen Sie eine Zeile und geben Sie Ihren Namen ... Auch IO ist rein, so dass Sie nicht einfach etwas ausführen können. Stattdessen bauen Sie eine größere IO Berechnung bis

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

ganz Imperativ aussieht, aber unter der Haube, es ist nur Syntax für

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

Jetzt können Sie diese bind/>>= für beliebige Arten von Berechnungen definieren in irgendeiner Weise, die Sie mögen. In der Tat kann alles, worüber Sie gesprochen haben, auf diese Weise implementiert werden - sogar FRP.

Suchen Sie einfach nach Monaden oder Haskell hier auf Stackoverflow; Es gab viele Fragen zu diesem Thema. Und schließlich ist es immer noch alles typgeprüft und somit kann Korrektheit vom Compiler durchgesetzt werden.

+0

Kleiner Zusatz: '>>' ist wie '>> =' außer, dass es das vorherige Ergebnis nicht übergibt (dh 'f >> g' ist äquivalent zu' f >> = (\\ _ -> g) 'where' _' ist der idiomatische Name für etwas unbenutztes). – delnan

+0

@ Delnan: Ja, richtig. Und '\ x -> irgendein Ausdruck 'ist die Syntax für eine anonyme Funktion, die einen Parameter' x 'nimmt. – Dario

+0

Ich sehe. Jetzt, wo ich darauf eingehe, nehme ich an, dass 'bind readln() als Eingabe' eine Art Monade ist. Und jetzt, da ich Haskell betrachte, ist es sehr interessant. Meine Ziele sind ein wenig anders als Haskell, aber es ist ein wirklich schöner Spielplatz und reich an Ideen. – rovaughn