È possibile la programmazione della GUI funzionale? [chiuso]


404

Di recente ho riscontrato il bug FP (cercando di imparare Haskell) e sono rimasto davvero colpito da ciò che ho visto finora (funzioni di prima classe, valutazione pigra e tutte le altre chicche). Non sono ancora un esperto, ma ho già iniziato a trovare più facile ragionare "funzionalmente" che in modo imperativo per gli algoritmi di base (e ho problemi a tornare dove devo).

L'area in cui l'attuale FP sembra fallire, tuttavia, è la programmazione della GUI. L'approccio di Haskell sembra essere semplicemente quello di avvolgere i toolkit GUI imperativi (come GTK + o wxWidgets) e usare i blocchi "do" per simulare uno stile imperativo. Non ho usato F #, ma la mia comprensione è che fa qualcosa di simile usando OOP con le classi .NET. Ovviamente, c'è una buona ragione per questo: l'attuale programmazione della GUI è tutta incentrata su IO ed effetti collaterali, quindi la programmazione puramente funzionale non è possibile con la maggior parte dei framework attuali.

La mia domanda è: è possibile avere un approccio funzionale alla programmazione della GUI? Ho difficoltà a immaginare come sarebbe questo in pratica. Qualcuno sa di eventuali framework, sperimentali o meno, che provano questo genere di cose (o anche qualsiasi framework progettato da zero per un linguaggio funzionale)? Oppure la soluzione è usare solo un approccio ibrido, con OOP per le parti della GUI e FP per la logica? (Sto solo chiedendo per curiosità: mi piacerebbe pensare che FP sia "il futuro", ma la programmazione della GUI sembra un buco piuttosto grande da riempire.)


7
Avendo esaminato le GUI in Common Lisp e OCaml, direi che, più probabilmente, la pigrizia di Haskell sta causando il problema.
nuovo123456,

5
@ new123456 Common Lisp non è un linguaggio funzionale, funziona con dati mutabili e abbraccia effetti collaterali
Electric Coffee,

3
@ElectricCoffee Lisp è un linguaggio estremamente flessibile che può essere utilizzato in molti stili diversi e molte persone scelgono di usare Lisp in uno stile funzionale.
chrismamo1,

8
Dalla mia esperienza (anche se sto ancora cercando di crederci e saperne di più) FRP raggiunge davvero il suo limite con la programmazione della GUI; è bello ed elegante per l'80% dei casi d'uso, ma i widget ricchi richiedono un controllo molto preciso del loro stato interno (ad esempio caselle combinate di ricerca, ecc.) e FRP si mette in mezzo. L'imperativo non è sempre malvagio; cercare di ridurre al minimo la quantità di codice imperativo va bene ma rimuoverlo al 100%? Devo ancora vederlo funzionare per uno sviluppo dell'interfaccia utente non banale.
AlexG

8
@ElectricCoffee "Tuttavia, Common Lisp non è un linguaggio funzionale". Lisp è la madre di tutti i linguaggi funzionali. Vuoi dire che Lisp non è puro.
Jon Harrop,

Risposte:


184

L'approccio di Haskell sembra essere semplicemente quello di avvolgere i toolkit GUI imperativi (come GTK + o wxWidgets) e usare i blocchi "do" per simulare uno stile imperativo

Non è proprio l '"approccio Haskell" - è così che ti colleghi ai kit di strumenti della GUI imperativa più direttamente - tramite un'interfaccia imperativa. Haskell sembra avere attacchi abbastanza importanti.

Esistono diversi approcci moderatamente maturi o più sperimentali puramente funzionali / dichiarativi alle GUI, principalmente in Haskell, e principalmente utilizzando la programmazione reattiva funzionale.

Alcuni esempi sono:

Per quelli di voi che non hanno familiarità con Haskell, Flapjax, http://www.flapjax-lang.org/ è un'implementazione della programmazione reattiva funzionale su JavaScript.


32
Vedi l'articolo di Conal Elliott sulla frutta per una descrizione approfondita e approfondita della tecnica e delle decisioni: conal.net/papers/genuinely-functional-guis.pdf Da alcuni mesi sto programmando una GUI puramente funzionale in questo stile . Lo adoro, è un piacevole sollievo dall'inferno della programmazione imperativa dell'interfaccia utente, che a questo riguardo sembra peggiore della maggior parte della programmazione imperativa.
luqui,

44
Sono d'accordo al 100% con questo. Per renderlo chiarissimo: il motivo per cui vengono spesso utilizzati i toolkit GUI esistenti è perché esistono. Il motivo per cui le interfacce verso di loro tendono ad essere imperative e impure è perché i toolkit tendono ad essere imperativi e impuri. Il motivo per cui i toolkit tendono ad essere imperativi e impuri è perché i sistemi operativi da cui dipendono tendono ad essere imperativi e impuri. Tuttavia, non c'è nulla che richieda fondamentalmente che uno di questi sia impuro: ci sono associazioni funzionali per quei toolkit, ci sono toolkit funzionali, ci sono persino sistemi operativi funzionali.
Jörg W Mittag,

16
È solo una questione di pigrizia. (Cattivo gioco di parole).
Jörg W Mittag,

10
Un giorno tutta la progettazione della GUI sarà implementata tramite WYSIWYG, con la logica implementata funzionalmente. Questa è la mia previsione.
BlueRaja - Danny Pflughoeft,

24
Il documento che Luqui menziona sembra essere morto. Esiste un link funzionante sul sito di Conal Elliott, tuttavia: conal.net/papers/genuinely-functional-guis.pdf
aganders3

74

La mia domanda è: è possibile avere un approccio funzionale alla programmazione della GUI?

Le parole chiave che stai cercando sono "programmazione reattiva funzionale" (FRP).

Conal Elliott e alcuni altri hanno fatto un po 'un'industria dei cottage cercando di trovare l'astrazione giusta per FRP. Esistono diverse implementazioni dei concetti di FRP in Haskell.

Potresti prendere in considerazione l'idea di iniziare con il più recente articolo di Conal "Programmazione reattiva funzionale push-pull" , ma ci sono molte altre implementazioni (più vecchie), alcune collegate al sito haskell.org . Conal ha un talento per coprire l'intero dominio e il suo documento può essere letto senza riferimento a ciò che è accaduto prima.

Per avere un'idea di come questo approccio può essere utilizzato per lo sviluppo della GUI, potresti voler guardare a Fudgets , che mentre sta diventando un po 'lungo nel dente in questi giorni, essendo progettato a metà degli anni '90, presenta un solido approccio FRP alla progettazione della GUI.


Vorrei aggiungere l'ascesa dell'uso di "Reactive Extensions" (librerie FRP; tuttavia, non FP) che è stato originariamente scritto per C # e poi portato su Java (RxJava) e JavaScript (RxJS) e varie lingue. Dai un'occhiata a reattivex.io Al punto, Angular 2 fa ampio uso di RxJS.
srph

63

Windows Presentation Foundation è la prova che l'approccio funzionale funziona molto bene per la programmazione della GUI. Ha molti aspetti funzionali e il "buon" codice WPF (ricerca del modello MVVM) sottolinea l'approccio funzionale all'imperativo. Potrei affermare coraggiosamente che WPF è il toolkit GUI funzionale di maggior successo del mondo reale :-)

WPF descrive l'interfaccia utente in XAML (anche se puoi riscriverla in modo che funzioni in modo funzionale anche in C # o F #), quindi per creare un'interfaccia utente scrivi:

<!-- 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>

Inoltre, WPF ti consente anche di descrivere in modo dichiarativo animazioni e reazioni agli eventi usando un altro set di tag dichiarativi (di nuovo, la stessa cosa può essere scritta come codice C # / F #):

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

In effetti, penso che WPF abbia molte cose in comune con FRP di Haskell (anche se credo che i progettisti WPF non sapessero di FRP ed è un po 'sfortunato - a volte WPF si sente un po' strano e poco chiaro se stai usando il funzionale punto di vista).


12
Mentre XAML è di natura molto dichiarativa, MVVM incoraggia davvero uno stile funzionale di programmazione? L'intera nozione di un modello di vista, il cui compito è tracciare lo stato della vista (e implementa un'interfaccia chiamata INotifyPropertyChangeddi tutte le cose), mi sembra antitetico a FP. Sicuramente non sono un esperto di FP, e forse mi sto concentrando troppo sull'aspetto dell'immutabilità rispetto all'aspetto dichiarativo, ma ho problemi a vedere come il modello MVVM (come comunemente usato) sia un esempio di FP.
devuxer,

1
@devuxer Direi che lo fa. Non penso che nessuno userebbe realisticamente FP per un codice immutabile rigoroso. Invece, decidi dove sono i tuoi limiti di mutabilità e lavori immutabile su tutti gli altri livelli - in questo caso, tutti possono presumere che lo stato sia immutabile, fatta eccezione per quella singola piccola parte che muta effettivamente lo stato. È simile a come funziona l'HTML: sì, hai il DOM immutabile, ma ogni volta che navighi, devi ancora costruirne uno nuovo. INotifyPropertyChangedè solo una funzione di aggiornamento che passi ovunque sia necessario per gestire gli aggiornamenti della GUI: è una correzione di latenza.
Luaan,

3
Steven Pemberton ha scritto 2 grandi post su F # e WPF, i suoi pensieri sullo sviluppo di WPF con F # verso la fine del secondo post si aggiungono a questa discussione. Altri 2 esempi che mi hanno anche incuriosito sono stati l'uso di un controller funzionale in MVVM guidato da eventi e l'uso di sindacati discriminati e la ricorsione per costruire una semplice interfaccia nella demo dei controlli WPF di Flying Frog Consultancy.
Funk,

29

In realtà direi che la programmazione funzionale (F #) è uno strumento molto migliore per la programmazione dell'interfaccia utente rispetto ad esempio a C #. Devi solo pensare al problema in modo leggermente diverso.

Discuto questo argomento nel mio libro di programmazione funzionale nel Capitolo 16, ma è disponibile un estratto gratuito che mostra (IMHO) il modello più interessante che puoi usare in F #. Supponiamo che tu voglia implementare il disegno di rettangoli (l'utente preme il pulsante, sposta il mouse e rilascia il pulsante). In F #, puoi scrivere qualcosa del genere:

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 }

Questo è un approccio molto imperativo (nel solito stile pragmatico F #), ma evita di usare lo stato mutabile per memorizzare lo stato corrente del disegno e per memorizzare la posizione iniziale. Può essere reso ancora più funzionale, ho scritto una biblioteca che lo fa come parte della mia tesi di laurea, che dovrebbe essere disponibile sul mio blog nei prossimi due giorni.

La programmazione reattiva funzionale è un approccio più funzionale, ma trovo un po 'più difficile da usare in quanto si basa su funzionalità Haskell abbastanza avanzate (come le frecce). Tuttavia, è molto elegante in un gran numero di casi. Il limite è che non è possibile codificare facilmente una macchina a stati (che è un utile modello mentale per i programmi reattivi). Questo è molto semplice usando la tecnica F # sopra.


7
+1 Questo riflette la nostra esperienza, avendo scritto diverse GUI di produzione in F # usando librerie combinatorie e IObservable.
Jon Harrop,

Il commento su FRP è cambiato dall'introduzione delle estensioni reattive alla libreria .NET?
Fsharp Pete,

1
Ecco alcune ricerche sul FRP Arrowized e su come gli effetti e le mutazioni possano essere incorporati all'interno del FRP Arrowized senza infrangere le leggi: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (anche se la maggior parte delle librerie FRP usa Monade o anche Applicativi, quindi non è corretto che siano necessarie le Frecce).
Erik Kaplun,

17

Che tu sia in un linguaggio ibrido funzionale / OO come F # o OCaml, o in un linguaggio puramente funzionale come Haskell in cui gli effetti collaterali sono relegati nella monade IO, è soprattutto il caso che una tonnellata di lavoro richieda la gestione di una GUI è molto più simile a un "effetto collaterale" che a un algoritmo puramente funzionale.

Detto questo, c'è stata una ricerca davvero solida messa in GUI funzionali . Esistono persino alcuni toolkit (principalmente) funzionali come Fudgets o FranTk .


6
Collegamento "GUI funzionali" interrotto :( memorizzato nella cache: webcache.googleusercontent.com/search?q=cache:http://…
Dan Burton


12

Una delle idee che aprono la mente dietro la Programmazione reattiva funzionale è quella di avere una funzione di gestione degli eventi che produca ENTRAMBE la reazione agli eventi E la successiva funzione di gestione degli eventi. Pertanto un sistema in evoluzione è rappresentato come una sequenza di funzioni di gestione degli eventi.

Per me, l'apprendimento di Yampa è diventato un punto cruciale per ottenere correttamente quella cosa che produce funzioni. Ci sono dei bei documenti su Yampa. Raccomando The Yampa Arcade:

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf (diapositive, PDF) http://www.cs.nott.ac.uk/~nhn/Publications/hw2003. pdf (articolo completo, PDF)

C'è una pagina wiki su Yampa su Haskell.org

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

Home page originale di Yampa:

http://www.haskell.org/yampa (purtroppo al momento è rotto)


1
Tale collegamento è interrotto da molto tempo. Prova questo Yampa
CoR il

7

Da quando questa domanda è stata posta per la prima volta, la programmazione reattiva funzionale è stata resa un po 'più diffusa da Elm.

Suggerisco di provarlo su http://elm-lang.org , che ha anche dei tutorial interattivi davvero eccellenti su come realizzare una GUI in-browser completamente funzionale.

Ti consente di realizzare GUI completamente funzionali in cui il codice che devi fornire è costituito solo da funzioni pure. Personalmente ho trovato molto più facile entrare nei vari framework della GUI di Haskell.



6

Il discorso di Elliot su FRP può essere trovato qui .

Inoltre, non proprio una risposta, ma un'osservazione e alcuni pensieri : in qualche modo il termine "GUI funzionale" sembra un po 'come un ossimoro (purezza e IO nello stesso termine).

Ma la mia vaga comprensione è che la programmazione della GUI funzionale riguarda la definizione dichiarativa di una funzione dipendente dal tempo che accetta l'input dell'utente (reale) dipendente dal tempo e produce un output GUI dipendente dal tempo.

In altre parole, questa funzione è definita in modo dichiarativo come un'equazione differenziale, invece che da un algoritmo che utilizza imperativamente lo stato mutabile.

Quindi nel FP convenzionale si usano funzioni indipendenti dal tempo, mentre in FRP si usano le funzioni dipendenti dal tempo come elementi costitutivi per la descrizione di un programma.

Pensiamo a simulare una palla su una molla con cui l'utente può interagire. La posizione della palla è l'output grafico (sullo schermo), l'utente che spinge la palla è un tasto premuto (input).

La descrizione di questo programma di simulazione in FRP (secondo la mia comprensione) è fatta da una singola equazione differenziale (dichiarativa): accelerazione * massa = - tratto di molla * costante di molla + Forza esercitata dall'utente.

Ecco un video su ELM che illustra questo punto di vista.


5

A partire dal 2016, ci sono molti altri framework FRP relativamente maturi per Haskell come Sodium e Reflex (ma anche Netwire).

Il libro Manning sulla programmazione reattiva funzionale mostra la versione Java di Sodium, per esempi di lavoro, e illustra come una base di codice GUI FRP si comporta e scala rispetto agli approcci imperativi e basati su attori.

C'è anche un recente articolo su FRP Arrowized e la prospettiva di incorporare effetti collaterali, IO e mutazione in un contesto rispettoso della legge, puro FRP: http://haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formatted-dissertation.pdf .

Vale anche la pena notare che i framework JavaScript come ReactJS e Angular e molti altri sono già o si stanno muovendo verso l'utilizzo di un approccio FRP o altrimenti funzionale per ottenere componenti GUI scalabili e componibili.


Il sodio è stato deprecato a favore della banana reattiva secondo il readme github di sodio
mac10688


3

Per rispondere a questo ho pubblicato alcuni miei pensieri nell'uso di F #,

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

Sto anche pianificando di fare un tutorial video per completare la serie e mostrare come F # può contribuire alla programmazione UX.

Sto parlando solo nel contesto di F # qui.

-Fahad


2

Tutte queste altre risposte si basano sulla programmazione funzionale, ma prendono molte delle proprie decisioni di progettazione. Una libreria che è costruita fondamentalmente interamente da funzioni e semplici tipi di dati astratti è gloss. Ecco il tipo per la sua playfunzione dalla fonte

-- | 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 ()

Come puoi vedere, funziona interamente fornendo funzioni pure con semplici tipi astratti, che altre librerie ti aiutano.


1

L'innovazione più evidente notata dalle persone nuove a Haskell è che esiste una separazione tra il mondo impuro che si occupa di comunicare con il mondo esterno e il mondo puro di calcolo e algoritmi. Una domanda frequente per i principianti è "Come posso liberarmi IO, ovvero convertirmi IO ain a?" Il modo per farlo è usare monadi (o altre astrazioni) per scrivere codice che esegua effetti IO e catene. Questo codice raccoglie dati dal mondo esterno, ne crea un modello, esegue alcuni calcoli, possibilmente impiegando codice puro, e genera il risultato.

Per quanto riguarda il modello sopra, non vedo nulla di terribilmente sbagliato nel manipolare le GUI nella IOmonade. Il problema più grande che deriva da questo stile è che i moduli non sono più compostabili, cioè perdo la maggior parte delle mie conoscenze sull'ordine di esecuzione globale delle istruzioni nel mio programma. Per recuperarlo, devo applicare un ragionamento simile a quello del codice GUI imperativo e simultaneo. Nel frattempo, per il codice impuro, non GUI, l'ordine di esecuzione è ovvio a causa della definizione dell'operatore della IOmonade >==(almeno fintanto che esiste un solo thread). Per il codice puro, non importa affatto, tranne in casi angolari per aumentare le prestazioni o evitare le risultanti valutazioni .

La più grande differenza filosofica tra console e IO grafico è che i programmi che implementano il primo sono generalmente scritti in stile sincrono. Ciò è possibile perché esiste (lasciando da parte segnali e altri descrittori di file aperti) solo una fonte di eventi: il flusso di byte comunemente chiamato stdin. Le GUI sono intrinsecamente asincrone e devono reagire agli eventi della tastiera e ai clic del mouse.

Una filosofia popolare di fare l'IO asincrono in modo funzionale si chiama Programmazione reattiva funzionale (FRP). Recentemente ha avuto molta trazione in linguaggi impuri e non funzionali grazie a librerie come ReactiveX e framework come Elm. In breve, è come visualizzare elementi della GUI e altre cose (come file, orologi, allarmi, tastiera, mouse) come fonti di eventi, chiamate "osservabili", che emettono flussi di eventi. Questi eventi vengono combinate utilizzando gli operatori familiari come map, foldl, zip, filter, concat, join, ecc, per la produzione di nuovi flussi. Questo è utile perché lo stato del programma stesso può essere visto come scanl . map reactToEvents $ zipN <eventStreams>del programma, dove Nè uguale al numero di osservabili mai considerati dal programma.

Lavorare con gli osservabili FRP consente di recuperare la componibilità perché gli eventi in uno stream sono ordinati in tempo. Il motivo è che l'astrazione del flusso di eventi consente di visualizzare tutti gli oggetti osservabili come scatole nere. In definitiva, la combinazione di flussi di eventi con operatori restituisce alcuni ordini locali in fase di esecuzione. Questo mi obbliga ad essere molto più onesto su quali invarianti si basa il mio programma, in modo simile al modo in cui tutte le funzioni di Haskell devono essere referenzialmente trasparenti: se voglio estrarre dati da un'altra parte del mio programma, devo essere esplicito ad dichiarare un tipo appropriato per le mie funzioni. (La monade IO, essendo un linguaggio specifico del dominio per la scrittura di codice impuro, aggira efficacemente questo)


-22

La programmazione funzionale potrebbe essere passata da quando ero all'università, ma come ricordo il punto principale di un sistema di programmazione funzionale era quello di impedire al programmatore di creare qualsiasi "effetto collaterale". Tuttavia, gli utenti acquistano software a causa degli effetti collaterali creati, ad esempio l'aggiornamento di un'interfaccia utente.


25
Penso che tu abbia frainteso il punto: non è che la programmazione funzionale non abbia alcun effetto esterno sul mondo - ciò renderebbe tutti i programmi completamente inutili! Piuttosto, la programmazione funzionale consente di mettere in quarantena l'IO in modo da sapere quali bit lo utilizzano e quali bit no.
Tikhon Jelvis,

Whoooooooosh x20
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.