it-swarm.com.de

Ist eine funktionierende GUI-Programmierung möglich?

Ich habe kürzlich den Fehler FP (beim Versuch, Haskell zu lernen) entdeckt und war wirklich beeindruckt von dem, was ich bisher gesehen habe (erstklassige Funktionen, verzögerte Auswertung und all die anderen Extras) ). Ich bin noch kein Experte, aber ich habe bereits begonnen, es einfacher zu finden, "funktional" als zwingend für grundlegende Algorithmen zu argumentieren (und ich habe Probleme, dorthin zurückzukehren, wo ich muss).

Der einzige Bereich, in dem das aktuelle FP flach zu fallen scheint, ist die GUI-Programmierung. Der Haskell-Ansatz scheint darin zu bestehen, imperative GUI-Toolkits (wie GTK + oder wxWidgets) zu verpacken und "do" -Blöcke zu verwenden, um einen imperativen Stil zu simulieren. Ich habe F # nicht verwendet, aber ich verstehe, dass es mit OOP etwas Ähnliches mit .NET-Klassen macht. Es gibt natürlich einen guten Grund dafür - bei der aktuellen GUI-Programmierung geht es nur um IO und Nebenwirkungen, sodass mit den meisten aktuellen Frameworks keine rein funktionale Programmierung möglich ist.

Meine Frage ist, ist es möglich, einen funktionalen Ansatz für die GUI-Programmierung zu haben? Ich kann mir nur schwer vorstellen, wie das in der Praxis aussehen würde. Kennt jemand Frameworks, experimentelle oder andere, die diese Art von Dingen ausprobieren (oder sogar Frameworks, die von Grund auf für eine funktionale Sprache entwickelt wurden)? Oder ist die Lösung einfach ein hybrider Ansatz mit OOP für die GUI-Teile und FP für die Logik? (Ich frage nur aus Neugier - ich würde gerne denken, dass FP "die Zukunft" ist, aber die GUI-Programmierung scheint eine ziemlich große Lücke zu füllen.)

393
shosti

Der Haskell-Ansatz scheint darin zu bestehen, imperative GUI-Toolkits (wie GTK + oder wxWidgets) zu verpacken und "do" -Blöcke zu verwenden, um einen imperativen Stil zu simulieren

Das ist nicht wirklich der "Haskell-Ansatz" - so binden Sie sich direkt an wichtige GUI-Toolkits - über eine wichtige Schnittstelle. Haskell hat zufällig ziemlich prominente Bindungen.

Es gibt mehrere mäßig ausgereifte oder experimentellere rein funktionale/deklarative Ansätze für GUIs, meist in Haskell, und in erster Linie mit funktionaler reaktiver Programmierung.

Einige Beispiele sind:

Für diejenigen von Ihnen, die nicht mit Haskell vertraut sind, ist Flapjax http://www.flapjax-lang.org/ eine Implementierung der funktionalen reaktiven Programmierung im Vordergrund von JavaScript.

181
Don Stewart

Meine Frage ist, ist es möglich, einen funktionalen Ansatz für die GUI-Programmierung zu haben?

Die Schlüsselwörter, die Sie suchen, sind "Functional Reactive Programming" (FRP).

Conal Elliott und einige andere haben es sich zur Aufgabe gemacht, die richtige Abstraktion für FRP zu finden. In Haskell gibt es mehrere Implementierungen von FRP-Konzepten.

Sie könnten mit Conals neuestem "Push-Pull Functional Reactive Programming" -Papier beginnen, aber es gibt mehrere andere (ältere) Implementierungen, von denen einige von der haskell.org site verlinkt sind . Conal hat ein Händchen für die Abdeckung der gesamten Domäne, und sein Artikel kann ohne Bezug auf das, was vorher kam, gelesen werden.

Um ein Gefühl dafür zu bekommen, wie dieser Ansatz für die GUI-Entwicklung verwendet werden kann, sollten Sie sich Fudgets ansehen, das, obwohl es heutzutage ein bisschen langwierig ist, Mitte der 90er Jahre entwickelt wurde präsentiert einen soliden FRP-Ansatz für das GUI-Design.

72
Edward KMETT

Windows Presentation Foundation ist ein Beweis dafür, dass der funktionale Ansatz für die GUI-Programmierung sehr gut funktioniert. Es hat viele funktionale Aspekte und "guter" WPF-Code (Suche nach MVVM-Mustern) betont den funktionalen Ansatz gegenüber dem Imperativ. Ich könnte mutig behaupten, dass WPF das erfolgreichste funktionale GUI-Toolkit in der realen Welt ist :-)

WPF beschreibt die Benutzeroberfläche in XAML (obwohl Sie sie auch so umschreiben können, dass sie funktional wie C # oder F # aussieht). So erstellen Sie eine Benutzeroberfläche, die Sie schreiben würden:

<!-- Declarative user interface in WPF and XAML --> 
<Canvas Background="Black">
   <Ellipse x:Name="greenEllipse" Width="75" Height="75" 
      Canvas.Left="0" Canvas.Top="0" Fill="LightGreen" />
</Canvas>

Darüber hinaus können Sie in WPF Animationen und Reaktionen auf Ereignisse mithilfe eines anderen Satzes deklarativer Tags deklarativ beschreiben (dasselbe kann auch als C #/F # -Code geschrieben werden):

<DoubleAnimation
   Storyboard.TargetName="greenEllipse" 
   Storyboard.TargetProperty="(Canvas.Left)"
   From="0.0" To="100.0" Duration="0:0:5" />

Tatsächlich denke ich, dass WPF viele Dinge mit Haskells FRP gemeinsam hat (obwohl ich glaube, dass WPF-Designer nichts über FRP wussten und es ein bisschen bedauerlich ist - WPF fühlt sich manchmal etwas seltsam und unklar an, wenn Sie die Funktion verwenden Perspektive).

61
Tomas Petricek

Eigentlich würde ich sagen, dass funktionale Programmierung (F #) ein viel besseres Werkzeug für die Programmierung von Benutzeroberflächen ist als zum Beispiel C #. Sie müssen sich das Problem nur ein wenig anders überlegen.

Ich diskutiere dieses Thema in meiner funktionalen Programmierung Buch in Kapitel 16, aber es gibt einen kostenlosen Auszug , der (IMHO) das interessanteste Muster zeigt, das Sie in F # verwenden können . Angenommen, Sie möchten das Zeichnen von Rechtecken implementieren (Benutzer drückt die Taste, bewegt die Maus und lässt die Taste los). In F # können Sie so etwas schreiben:

let rec drawingLoop(clr, from) = async { 
   // Wait for the first MouseMove occurrence 
   let! move = Async.AwaitObservable(form.MouseMove) 
   if (move.Button &&& MouseButtons.Left) = MouseButtons.Left then 
      // Refresh the window & continue looping 
      drawRectangle(clr, from, (move.X, move.Y)) 
      return! drawingLoop(clr, from) 
   else
      // Return the end position of rectangle 
      return (move.X, move.Y) } 

let waitingLoop() = async { 
   while true do
      // Wait until the user starts drawing next rectangle
      let! down = Async.AwaitObservable(form.MouseDown) 
      let downPos = (down.X, down.Y) 
      if (down.Button &&& MouseButtons.Left) = MouseButtons.Left then 
         // Wait for the end point of the rectangle
         let! upPos = drawingLoop(Color.IndianRed, downPos) 
         do printfn "Drawn rectangle (%A, %A)" downPos upPos }

Dies ist ein sehr zwingender Ansatz (im üblichen pragmatischen F # -Stil), der jedoch die Verwendung eines veränderlichen Zustands zum Speichern des aktuellen Zeichnungszustands und zum Speichern der ursprünglichen Position vermeidet. Es kann jedoch noch funktionaler gestaltet werden, da ich im Rahmen meiner Masterarbeit eine Bibliothek geschrieben habe, die in den nächsten Tagen auf meinem Blog verfügbar sein sollte.

Functional Reactive Programming ist ein eher funktionaler Ansatz, aber ich finde es etwas schwieriger, ihn zu verwenden, da er sich auf recht fortgeschrittene Haskell-Funktionen (wie Pfeile) stützt. Es ist jedoch in vielen Fällen sehr elegant. Die Einschränkung besteht darin, dass Sie eine Zustandsmaschine nicht einfach codieren können (was ein nützliches mentales Modell für reaktive Programme ist). Mit der oben beschriebenen F # -Technik ist dies sehr einfach.

27
Tomas Petricek

Egal, ob Sie in einer hybriden funktionalen/OO-Sprache wie F # oder OCaml oder in einer rein funktionalen Sprache wie Haskell arbeiten, bei der die Nebenwirkungen auf die IO= Monade zurückgeführt werden, es ist Meistens ist ein Großteil der für die Verwaltung einer GUI erforderlichen Arbeit eher ein "Nebeneffekt" als ein rein funktionaler Algorithmus.

Das heißt, es wurden einige wirklich solide Untersuchungen zu funktionellen GUIs durchgeführt. Es gibt sogar einige (meistens) funktionale Toolkits wie Fudgets oder FranTk .

17
sblom

Sie können sich die Serie von Don Syme auf F # ansehen, in der er eine GUI erstellt. Der folgende Link verweist auf den dritten Teil der Serie (von dort können Sie auf die beiden anderen Teile verweisen).

Die Verwendung von F # für die WPF-Entwicklung wäre ein sehr interessantes GUI-Paradigma ...

http://channel9.msdn.com/shows/Going+Deep/C9-Lectures-Dr-Don-Syme-Introduction-to-F-3-of-3/

15
Kevin Won

Eine der aufschlussreichen Ideen hinter Functional Reactive Programming ist, eine Ereignisbehandlungsfunktion zu haben, die BEIDE Reaktionen auf Ereignisse UND die nächste Ereignisbehandlungsfunktion erzeugt. Somit wird ein sich entwickelndes System als eine Folge von Ereignisbehandlungsfunktionen dargestellt.

Das Lernen von Yampa wurde für mich zu einem entscheidenden Faktor, um die Dinge, die Funktionen produzieren, richtig zu machen. Es gibt einige nette Zeitungen über Yampa. Ich empfehle The Yampa Arcade:

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf (Folien, PDF) http: //www.cs.nott. ac.uk/~nhn/Publications/hw2003.pdf (vollständiger Artikel, PDF)

Es gibt eine Wiki-Seite zu Yampa auf Haskell.org

http://www.haskell.org/haskellwiki/Yampa

Ursprüngliche Yampa-Homepage:

http://www.haskell.org/yampa (ist im Moment leider kaputt)

12
Boris Berkgaut

Seitdem diese Frage zum ersten Mal gestellt wurde, wurde die funktionale reaktive Programmierung von Elm ein bisschen mehr in den Mainstream integriert.

Ich schlage vor, dass Sie es unter http://Elm-lang.org nachlesen. Dort finden Sie auch einige wirklich hervorragende interaktive Tutorials, wie Sie eine voll funktionsfähige Benutzeroberfläche im Browser erstellen.

Sie können damit voll funktionsfähige GUIs erstellen, bei denen der Code, den Sie selbst bereitstellen müssen, nur aus reinen Funktionen besteht. Ich persönlich fand es viel einfacher, in die verschiedenen Haskell-GUI-Frameworks einzusteigen.

6
saolof

Elliots Vortrag über FRP ist zu finden hier .

Außerdem nicht wirklich eine Antwort, sondern eine Bemerkung und ein paar Gedanken : Irgendwie wirkt der Begriff "funktionale Benutzeroberfläche" ein wenig wie ein Oxymoron (Reinheit und IO im selben Begriff).

Mein vages Verständnis ist jedoch, dass es bei der funktionalen GUI-Programmierung darum geht, eine zeitabhängige Funktion deklarativ zu definieren, die die (echtzeitabhängige) Benutzereingabe verwendet und eine zeitabhängige GUI-Ausgabe erzeugt.

Mit anderen Worten, diese Funktion wird deklarativ wie eine Differentialgleichung definiert, anstatt durch einen Algorithmus, der zwingend einen veränderlichen Zustand verwendet.

In konventionellen FP verwendet man zeitunabhängige Funktionen, während in FRP zeitabhängige Funktionen als Bausteine ​​zur Beschreibung eines Programms verwendet werden.

Denken wir an die Simulation einer Kugel auf einer Feder, mit der der Benutzer interagieren kann. Die Position des Balls ist die grafische Ausgabe (auf dem Bildschirm), der Benutzer drückt den Ball per Tastendruck (Eingabe).

Die Beschreibung dieses Simulationsprogramms in FRP (nach meinem Verständnis) erfolgt durch eine einzige Differentialgleichung (deklarativ): Beschleunigung * Masse = - Federdehnung * Federkonstante + vom Benutzer ausgeübte Kraft.

Hier ist ein Video über Elm , das diesen Standpunkt veranschaulicht.

6
jhegedus

Ab 2016 gibt es für Haskell mehrere relativ ausgereifte FRP-Frameworks wie Sodium und Reflex (aber auch Netwire).

Das Handbuch zur funktionalen reaktiven Programmierung zeigt die Java) - Version von Sodium als Arbeitsbeispiel und zeigt, wie sich eine FRP-GUI-Codebasis im Vergleich zu imperativem Code verhält und skaliert sowie auf Akteuren basierende Ansätze.

Es gibt auch ein kürzlich veröffentlichtes Papier über Arrowized FRP und die Möglichkeit, Nebenwirkungen, IO und Mutationen in eine gesetzestreue, reine FRP-Einstellung zu integrieren: http: //haskell.cs.yale .edu/wp-content/uploads/2015/10/dwc-yale-formatierte-Dissertation.pdf .

Erwähnenswert ist auch, dass JavaScript-Frameworks wie ReactJS und Angular und viele andere bereits einen FRP- oder einen anderen funktionalen Ansatz verwenden, um skalierbare und zusammensetzbare GUI-Komponenten zu erzielen.

5
Erik Allik

Mit Auszeichnungssprachen wie XUL können Sie eine GUI deklarativ erstellen.

4
StackedCrooked

Um dieses Problem zu lösen, habe ich einige meiner Gedanken in Bezug auf die Verwendung von F # veröffentlicht.

http://fadsworld.wordpress.com/2011/04/13/f-in-the-enterprise-i/ http://fadsworld.wordpress.com/2011/04/17/fin -das-unternehmen-ii-2 /

Ich plane auch ein Video-Tutorial, um die Serie zu beenden und zu zeigen, wie F # zur UX-Programmierung beitragen kann.

Ich spreche hier nur im Zusammenhang mit F #.

-Fahad

4
Fahad

Alle diese anderen Antworten basieren auf funktionaler Programmierung, treffen jedoch viele eigene Designentscheidungen. Eine Bibliothek, die hauptsächlich aus Funktionen und einfachen abstrakten Datentypen besteht, ist gloss . Hier ist der Typ für die Funktion play aus der Quelle

-- | Play a game in a window. Like `simulate`, but you manage your own input events.
play    :: Display              -- ^ Display mode.
        -> Color                -- ^ Background color.
        -> Int                  -- ^ Number of simulation steps to take for each second of real time.
        -> world                -- ^ The initial world.
        -> (world -> Picture)   -- ^ A function to convert the world a picture.
        -> (Event -> world -> world)    
                -- ^ A function to handle input events.
        -> (Float -> world -> world)
                -- ^ A function to step the world one iteration.
                --   It is passed the period of time (in seconds) needing to be advanced.
        -> IO ()

Wie Sie sehen, funktioniert es vollständig, indem reine Funktionen mit einfachen abstrakten Typen bereitgestellt werden, bei denen Ihnen andere Bibliotheken helfen.

2
PyRulez

Die augenscheinlichste Neuerung, die Haskell-Neulingen auffällt, ist die Trennung zwischen der unreinen Welt, in der es um die Kommunikation mit der Außenwelt geht, und der reinen Welt der Berechnungen und Algorithmen. Eine häufige Anfängerfrage lautet: "Wie kann ich IO loswerden, d. H. IO a In a konvertieren?" Der Weg dahin besteht darin, Monaden (oder andere Abstraktionen) zu verwenden, um Code zu schreiben, der IO ausführt und Effekte verkettet. Dieser Code sammelt Daten aus der Außenwelt, erstellt ein Modell davon, führt einige Berechnungen durch, möglicherweise unter Verwendung von reinem Code, und gibt das Ergebnis aus.

Was das obige Modell anbelangt, sehe ich in der IO -Monade keine schrecklichen Fehler bei der Manipulation von GUIs. Das größte Problem, das sich aus diesem Stil ergibt, ist, dass Module nicht mehr zusammensetzbar sind, d. H. Ich verliere den größten Teil meines Wissens über die globale Ausführungsreihenfolge von Anweisungen in meinem Programm. Um es wiederherzustellen, muss ich ähnliche Überlegungen anstellen wie bei gleichzeitigem, zwingendem GUI-Code. In der Zwischenzeit ist für unreinen Code ohne GUI die Ausführungsreihenfolge aufgrund der Definition des Operators IO monad >== Offensichtlich (zumindest solange es nur einen Thread gibt). Für reinen Code spielt es keine Rolle, außer in Eckfällen, um die Leistung zu steigern oder Auswertungen zu vermeiden, die zu Führen.

Der größte philosophische Unterschied zwischen Konsole und grafischem IO besteht darin, dass Programme, die das erstere implementieren, normalerweise synchron geschrieben sind. Dies ist möglich, weil es (abgesehen von Signalen und anderen offenen Dateideskriptoren) nur eine Ereignisquelle gibt: den üblicherweise als stdin bezeichneten Byte-Stream. GUIs sind von Natur aus asynchron und müssen auf Tastaturereignisse und Mausklicks reagieren.

Eine weit verbreitete Philosophie, asynchrones IO auf funktionale Weise auszuführen, heißt Functional Reactive Programming (FRP). Dank Bibliotheken wie ReactiveX und Frameworks wie Elm hat es in letzter Zeit in unreinen, nicht funktionalen Sprachen viel Anklang gefunden. Kurz gesagt, es ist wie das Anzeigen von GUI-Elementen und anderen Dingen (wie Dateien, Uhren, Alarmen, Tastaturen, Mäusen) als Ereignisquellen, die als "Observables" bezeichnet werden und Ereignisströme ausstrahlen. Diese Ereignisse werden mit bekannten Operatoren wie map, foldl, Zip, filter, concat und join kombiniert. usw., um neue Streams zu erzeugen. Dies ist nützlich, da der Programmstatus selbst als scanl . map reactToEvents $ zipN <eventStreams> Des Programms angesehen werden kann, wobei N der Anzahl der vom Programm jemals berücksichtigten Observablen entspricht.

Durch die Arbeit mit FRP-Observablen kann die Kompositionsfähigkeit wiederhergestellt werden, da die Ereignisse in einem Stream zeitlich geordnet sind. Der Grund dafür ist, dass die Ereignisstromabstraktion es ermöglicht, alle Observablen als Black Boxes anzuzeigen. Das Kombinieren von Ereignisströmen mithilfe von Operatoren gibt letztendlich eine gewisse lokale Reihenfolge bei der Ausführung zurück. Dies zwingt mich, ehrlicher darüber zu sein, auf welche Invarianten sich mein Programm tatsächlich stützt, ähnlich wie alle Funktionen in Haskell referenziell transparent sein müssen: Wenn ich Daten aus einem anderen Teil meines Programms ziehen möchte, muss ich explizit vorgehen ad deklariert einen geeigneten Typ für meine Funktionen. (Die IO -Monade, eine domänenspezifische Sprache zum Schreiben von unreinem Code, umgeht dies effektiv.)

1
MauganRa