La programmation fonctionnelle de l'interface graphique est-elle possible? [fermé]


405

J'ai récemment attrapé le bug FP (en essayant d'apprendre Haskell), et j'ai été vraiment impressionné par ce que j'ai vu jusqu'à présent (fonctions de première classe, évaluation paresseuse et tous les autres goodies). Je ne suis pas encore un expert, mais j'ai déjà commencé à trouver plus facile de raisonner "fonctionnellement" qu'impérativement pour les algorithmes de base (et j'ai du mal à revenir là où je dois).

Le seul domaine où la PF actuelle semble tomber à plat, cependant, est la programmation GUI. L'approche Haskell semble consister simplement à envelopper des kits d'outils GUI impératifs (tels que GTK + ou wxWidgets) et à utiliser des blocs "do" pour simuler un style impératif. Je n'ai pas utilisé F #, mais ma compréhension est qu'il fait quelque chose de similaire en utilisant OOP avec les classes .NET. De toute évidence, il y a une bonne raison à cela - la programmation actuelle de l'interface graphique concerne les E / S et les effets secondaires, donc la programmation purement fonctionnelle n'est pas possible avec la plupart des frameworks actuels.

Ma question est, est-il possible d'avoir une approche fonctionnelle de la programmation GUI? J'ai du mal à imaginer à quoi cela ressemblerait dans la pratique. Quelqu'un connaît-il des cadres, expérimentaux ou non, qui essaient ce genre de chose (ou même des cadres conçus à partir de zéro pour un langage fonctionnel)? Ou la solution consiste-t-elle à utiliser simplement une approche hybride, avec OOP pour les parties GUI et FP pour la logique? (Je demande juste par curiosité - j'adorerais penser que FP est "l'avenir", mais la programmation GUI semble être un assez grand trou à combler.)


7
Après avoir examiné les interfaces graphiques en Common Lisp et OCaml, je dirais que, plus probablement, la paresse de Haskell qui cause le problème.
new123456

5
@ new123456 Common Lisp n'est cependant pas un langage fonctionnel, il fonctionne avec des données mutables et embrasse les effets secondaires
Electric Coffee

3
@ElectricCoffee Lisp est un langage extrêmement flexible capable d'être utilisé dans de nombreux styles différents, et de nombreuses personnes choisissent d'utiliser Lisp dans un style fonctionnel.
chrismamo1

8
D'après mon expérience (bien que j'essaie encore d'y croire et d'en apprendre plus), FRP atteint vraiment ses limites avec la programmation GUI; c'est agréable et élégant pour 80% des cas d'utilisation, mais les widgets riches nécessitent un contrôle très précis de leur état interne (par exemple, des zones de liste déroulante de recherche, etc.) et FRP se met juste en travers. L'impératif n'est pas toujours mauvais; essayer de minimiser la quantité de code impératif est bien mais en supprimer 100%? Je ne l'ai pas encore vu fonctionner pour un développement d'interface utilisateur non trivial.
AlexG

8
@ElectricCoffee "Common Lisp n'est cependant pas un langage fonctionnel". Lisp est la mère de tous les langages fonctionnels. Tu veux dire que Lisp n'est pas pur.
Jon Harrop

Réponses:


185

L'approche Haskell semble être de simplement envelopper des kits d'outils GUI impératifs (tels que GTK + ou wxWidgets) et d'utiliser des blocs "do" pour simuler un style impératif

Ce n'est pas vraiment "l'approche Haskell" - c'est juste la façon dont vous vous connectez le plus directement aux kits d'outils GUI impératifs - via une interface impérative. Haskell se trouve juste avoir des reliures assez importantes.

Il existe plusieurs approches modérément matures ou plus expérimentales purement fonctionnelles / déclaratives des interfaces graphiques, principalement dans Haskell, et utilisant principalement la programmation réactive fonctionnelle.

Quelques exemples sont:

Pour ceux d'entre vous qui ne sont pas familiers avec Haskell, Flapjax, http://www.flapjax-lang.org/ est une implémentation de programmation réactive fonctionnelle en plus de JavaScript.


32
Voir l'article de Conal Elliott sur les fruits pour une excellente description approfondie de la technique et des décisions: conal.net/papers/genuinely-functional-guis.pdf Je fais de la programmation GUI purement fonctionnelle dans ce style depuis quelques mois maintenant . J'adore, c'est un soulagement tellement agréable de l'enfer spaghetti de la programmation impérative de l'interface utilisateur, qui semble être pire à cet égard que la plupart des programmes impératifs.
luqui

44
Je suis 100% d'accord avec cela. Pour être clair: la raison pour laquelle les boîtes à outils GUI existantes sont souvent utilisées est qu'elles existent. La raison pour laquelle les interfaces avec elles ont tendance à être impératives et impures est que les boîtes à outils ont tendance à être impératives et impures. La raison pour laquelle les kits d'outils ont tendance à être impératifs et impurs est que les systèmes d'exploitation dont ils dépendent ont tendance à être impératifs et impurs. Cependant, rien ne requiert fondamentalement que ces éléments soient impurs: il y a des liaisons fonctionnelles pour ces boîtes à outils, il y a des boîtes à outils fonctionnelles, il y a même des systèmes d'exploitation fonctionnels.
Jörg W Mittag

16
C'est juste une question de paresse. (Mauvais jeu de mots voulu.)
Jörg W Mittag

10
Un jour, toute la conception de l'interface graphique sera mise en œuvre via WYSIWYG, avec la logique implémentée de manière fonctionnelle. Ceci est ma prédiction.
BlueRaja - Danny Pflughoeft

24
Le papier mentionné par Luqui semble être mort. Il existe cependant un lien de travail sur le site de Conal Elliott: conal.net/papers/genuinely-functional-guis.pdf
aganders3

74

Ma question est, est-il possible d'avoir une approche fonctionnelle de la programmation GUI?

Les mots clés que vous recherchez sont "programmation réactive fonctionnelle" (FRP).

Conal Elliott et quelques autres ont fait un peu d'une industrie artisanale en essayant de trouver la bonne abstraction pour FRP. Il existe plusieurs implémentations des concepts FRP dans Haskell.

Vous pourriez envisager de commencer par le plus récent article de Conal "Push-Pull Functional Reactive Programming" de Conal , mais il existe plusieurs autres implémentations (plus anciennes), certaines liées depuis le site haskell.org . Conal a le don de couvrir tout le domaine et son article peut être lu sans référence à ce qui l'a précédé.

Pour avoir une idée de la façon dont cette approche peut être utilisée pour le développement d'interface graphique, vous voudrez peut-être regarder Fudgets , qui, bien qu'il soit un peu long dans la dent ces jours-ci, étant conçu au milieu des années 90, présente une approche FRP solide à la conception GUI.


Je voudrais ajouter la montée en puissance des "Reactive Extensions" (bibliothèques FRP; mais pas FP) qui a été à l'origine écrit pour C # puis porté sur Java (RxJava) et JavaScript (RxJS) et divers langages. Consultez reactivex.ioAu point, Angular 2 utilise largement RxJS.
srph

63

Windows Presentation Foundation est une preuve que l'approche fonctionnelle fonctionne très bien pour la programmation GUI. Il présente de nombreux aspects fonctionnels et un "bon" code WPF (recherche de modèle MVVM) met l'accent sur l'approche fonctionnelle plutôt que sur l'impératif. Je pourrais courageusement affirmer que WPF est la boîte à outils GUI fonctionnelle la plus réussie :-)

WPF décrit l'interface utilisateur en XAML (bien que vous puissiez également la réécrire en regardant fonctionnellement C # ou F #), donc pour créer une interface utilisateur, vous écririez:

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

De plus, WPF vous permet également de décrire de manière déclarative les animations et les réactions aux événements en utilisant un autre ensemble de balises déclaratives (encore une fois, la même chose peut être écrite en code C # / F #):

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

En fait, je pense que WPF a beaucoup de choses en commun avec le FRP de Haskell (bien que je pense que les concepteurs de WPF ne connaissaient pas FRP et c'est un peu malheureux - WPF se sent parfois un peu bizarre et peu clair si vous utilisez la fonctionnalité point de vue).


12
Alors que XAML est de nature très déclarative, MVVM encourage-t-il vraiment un style de programmation fonctionnel? Toute la notion de modèle de vue, dont le travail consiste à suivre l'état de la vue (et implémente une interface appelée INotifyPropertyChangedde toutes choses), me semble contraire à FP. Je ne suis certainement pas un expert en FP, et je me concentre peut-être trop sur l'aspect immuabilité par opposition à l'aspect déclaratif, mais j'ai du mal à voir comment le modèle MVVM (tel qu'il est généralement utilisé) est un exemple de FP.
devuxer

1
@devuxer Je dirais que oui. Je ne pense pas que quiconque utiliserait de manière réaliste FP pour un code immuable strict. Au lieu de cela, vous décidez où se trouvent vos limites de mutabilité et travaillez immuable à tous les autres niveaux - dans ce cas, tout le monde peut supposer que l'état est immuable, à l'exception de cette seule petite partie qui mute réellement l'état. C'est similaire au fonctionnement de HTML - oui, vous avez le DOM immuable, mais chaque fois que vous naviguez, vous devez toujours en créer un nouveau. INotifyPropertyChangedest juste une fonction de mise à jour que vous transmettez partout où vous avez besoin de gérer les mises à jour de l'interface graphique - c'est un correctif de latence.
Luaan

3
Steven Pemberton a écrit 2 excellents articles sur F # et WPF, ses réflexions sur le développement de WPF avec F # vers la fin du deuxième article ajoute à cette discussion. 2 autres exemples qui m'ont également intrigué étaient l'utilisation d'un contrôleur fonctionnel dans MVVM piloté par les événements et l'utilisation d'unions et de récursions discriminées pour construire une interface simple dans la démonstration des contrôles WPF par Flying Frog Consultancy.
Funk

29

Je dirais en fait que la programmation fonctionnelle (F #) est un bien meilleur outil pour la programmation d'interface utilisateur que par exemple C #. Vous avez juste besoin de penser le problème un peu différemment.

Je discute de ce sujet dans mon livre de programmation fonctionnelle au chapitre 16, mais il y a un extrait gratuit disponible , qui montre ( à mon humble avis) le modèle le plus intéressant que vous pouvez utiliser en F #. Supposons que vous souhaitiez implémenter le dessin de rectangles (l'utilisateur appuie sur le bouton, déplace la souris et relâche le bouton). En F #, vous pouvez écrire quelque chose comme ceci:

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 }

Il s'agit d'une approche très impérative (dans le style pragmatique habituel F #), mais elle évite d'utiliser l'état mutable pour stocker l'état actuel du dessin et pour stocker l'emplacement initial. Cela peut être rendu encore plus fonctionnel, j'ai écrit une bibliothèque qui fait cela dans le cadre de ma thèse de master, qui devrait être disponible sur mon blog dans les deux prochains jours.

La programmation réactive fonctionnelle est une approche plus fonctionnelle, mais je la trouve un peu plus difficile à utiliser car elle repose sur des fonctionnalités Haskell assez avancées (comme les flèches). Cependant, il est très élégant dans un grand nombre de cas. Sa limitation est que vous ne pouvez pas facilement encoder une machine à états (qui est un modèle mental utile pour les programmes réactifs). C'est très facile en utilisant la technique F # ci-dessus.


7
+1 Ceci reflète notre expérience, ayant écrit plusieurs GUI de production en F # en utilisant les bibliothèques combinatoires et IObservable.
Jon Harrop

Le commentaire sur FRP a-t-il changé depuis l'introduction des extensions réactives à la bibliothèque .NET?
Fsharp Pete

1
Voici quelques recherches sur le FRP Arrowized et comment les effets et la mutation peuvent être intégrés dans le FRP Arrowized sans enfreindre les lois: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (en fait, la plupart des bibliothèques FRP utilisent des monades ou même Candidats, il n'est donc pas correct que des flèches soient nécessaires).
Erik Kaplun

17

Que vous soyez dans un langage hybride fonctionnel / OO comme F # ou OCaml, ou dans un langage purement fonctionnel comme Haskell où les effets secondaires sont relégués à la monade IO, c'est généralement le cas qu'une tonne de travail est nécessaire pour gérer une interface graphique ressemble beaucoup plus à un "effet secondaire" qu'à un algorithme purement fonctionnel.

Cela dit, des recherches très solides ont été les interfaces graphiques fonctionnelles . Il existe même des boîtes à outils (principalement) fonctionnelles telles que Fudgets ou FranTk .


6
lien "interfaces graphiques fonctionnelles" rompu :( mis en cache: webcache.googleusercontent.com/search?q=cache:http://…
Dan Burton


12

L'une des idées révélatrices de la programmation fonctionnelle réactive est d'avoir une fonction de gestion des événements produisant à la fois une réaction aux événements ET la prochaine fonction de gestion des événements. Ainsi, un système en évolution est représenté comme une séquence de fonctions de gestion d'événements.

Pour moi, l'apprentissage de Yampa est devenu un point crucial pour obtenir correctement cette fonction de production de fonctions. Il y a de beaux papiers sur Yampa. Je recommande The Yampa Arcade:

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf (diapositives, PDF) http://www.cs.nott.ac.uk/~nhn/Publications/hw2003. pdf (article complet, PDF)

Il y a une page wiki sur Yampa sur Haskell.org

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

Page d'accueil Yampa originale:

http://www.haskell.org/yampa (malheureusement cassé pour le moment)


1
Ce lien est rompu depuis longtemps. Essayez ce Yampa
CoR

7

Depuis que cette question a été posée pour la première fois, la programmation réactive fonctionnelle a été rendue un peu plus courante par Elm.

Je suggère de le vérifier sur http://elm-lang.org , qui propose également d'excellents didacticiels interactifs sur la façon de créer une interface graphique entièrement fonctionnelle dans le navigateur.

Il vous permet de créer des interfaces graphiques entièrement fonctionnelles où le code que vous devez fournir vous-même n'est composé que de fonctions pures. Personnellement, je l'ai trouvé beaucoup plus facile à utiliser que les différents cadres de l'interface graphique Haskell.


Voici la thèse FRP originale derrière Elm . Mais aussi depuis mai 2016, Elm n'est plus une langue FRP .
icc97

6

Le discours d'Elliot sur FRP peut être trouvé ici .

De plus, pas vraiment une réponse mais une remarque et quelques réflexions : en quelque sorte le terme "GUI fonctionnel" ressemble un peu à un oxymore (pureté et IO dans le même terme).

Mais ma compréhension vague est que la programmation d'interface graphique fonctionnelle consiste à définir de manière déclarative une fonction dépendante du temps qui prend l'entrée utilisateur dépendante du temps (réel) et produit une sortie GUI dépendante du temps.

En d'autres termes, cette fonction est définie comme une équation différentielle de manière déclarative, plutôt que par un algorithme utilisant impérativement un état mutable.

Ainsi, dans FP conventionnel, on utilise des fonctions indépendantes du temps, tandis que dans FRP, on utilise des fonctions dépendantes du temps comme blocs de construction pour décrire un programme.

Pensons à simuler une balle sur un ressort avec lequel l'utilisateur peut interagir. La position de la balle est la sortie graphique (sur l'écran), l'utilisateur poussant la balle est une touche (entrée).

La description de ce programme de simulation en FRP (selon ma compréhension) se fait par une seule équation différentielle (déclarative): accélération * masse = - étirement du ressort * constante du ressort + Force exercée par l'utilisateur.

Voici une vidéo sur ELM qui illustre ce point de vue.


5

En 2016, il existe plusieurs autres cadres FRP relativement matures pour Haskell tels que Sodium et Reflex (mais aussi Netwire).

Le livre de Manning sur la programmation réactive fonctionnelle présente la version Java de Sodium, pour des exemples de travail, et illustre comment une base de code GUI FRP se comporte et évolue par rapport aux approches impératives et basées sur les acteurs.

Il y a aussi un article récent sur le FRP Arrowized et la perspective d'incorporer les effets secondaires, les IO et les mutations dans un cadre de FRP pur et respectueux de la loi: http://haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formated-dissertation.pdf .

Il convient également de noter que les frameworks JavaScript tels que ReactJS et Angular et bien d'autres sont déjà ou s'orientent vers l'utilisation d'un FRP ou d'une approche fonctionnelle pour réaliser des composants GUI évolutifs et composables.


Le sodium a été déprécié en faveur de la banane réactive selon le readme du github de Sodium
mac10688

4

Les langages de balisage comme XUL vous permettent de créer une interface graphique de manière déclarative.


3

Pour résoudre ce problème, j'ai publié mes réflexions sur l'utilisation de 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 /

Je prévois également de faire un tutoriel vidéo pour terminer la série et montrer comment F # peut contribuer à la programmation UX.

Je ne parle que dans le contexte de F # ici.

-Fahad


2

Toutes ces autres réponses reposent sur une programmation fonctionnelle, mais prennent beaucoup de leurs propres décisions de conception. Une bibliothèque qui est construite essentiellement entièrement à partir de fonctions et de types de données abstraits simples est gloss. Voici le type de sa playfonction à partir de la source

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

Comme vous pouvez le voir, cela fonctionne entièrement en fournissant des fonctions pures avec des types abstraits simples, que d'autres bibliothèques vous aident.


1

L'innovation la plus apparente remarquée par les nouveaux venus à Haskell est qu'il existe une séparation entre le monde impur qui s'intéresse à la communication avec le monde extérieur et le monde pur du calcul et des algorithmes. Une question fréquente pour les débutants est "Comment puis-je me débarrasser IO, c'est-à-dire se convertir IO aen a?" Le moyen d'y parvenir est d'utiliser des monades (ou d'autres abstractions) pour écrire du code qui effectue des effets d'E / S et des chaînes. Ce code rassemble des données du monde extérieur, en crée un modèle, effectue certains calculs, éventuellement en utilisant du code pur, et génère le résultat.

En ce qui concerne le modèle ci-dessus, je ne vois rien de mal à manipuler les interfaces graphiques dans la IOmonade. Le plus gros problème qui découle de ce style est que les modules ne sont plus composables, c'est-à-dire que je perds la plupart de mes connaissances sur l'ordre d'exécution global des instructions dans mon programme. Pour le récupérer, je dois appliquer un raisonnement similaire à celui du code GUI impératif simultané. Pendant ce temps, pour un code impur non GUI, l'ordre d'exécution est évident en raison de la définition de l' opérateur de la IOmonade >==(au moins tant qu'il n'y a qu'un seul thread). Pour le code pur, cela n'a pas d'importance du tout, sauf dans les cas d'angle pour augmenter les performances ou pour éviter des évaluations entraînant .

La plus grande différence philosophique entre console et IO graphique est que les programmes implémentant le premier sont généralement écrits dans un style synchrone. Ceci est possible car il n'y a (en laissant de côté les signaux et autres descripteurs de fichiers ouverts) qu'une seule source d'événements: le flux d'octets communément appeléstdin . Les interfaces graphiques sont cependant intrinsèquement asynchrones et doivent réagir aux événements du clavier et aux clics de souris.

Une philosophie populaire consistant à effectuer des E / S asynchrones de manière fonctionnelle est appelée programmation réactive fonctionnelle (FRP). Il a obtenu beaucoup de traction récemment dans des langages impurs et non fonctionnels grâce à des bibliothèques telles que ReactiveX et des frameworks tels que Elm. En un mot, c'est comme visualiser les éléments de l'interface graphique et d'autres choses (comme les fichiers, les horloges, les alarmes, le clavier, la souris) en tant que sources d'événements, appelées "observables", qui émettent des flux d'événements. Ces événements sont combinés à l' aide des opérateurs familiers tels que map, foldl, zip, filter, concat, join, etc., pour produire de nouveaux flux. Ceci est utile car l'état du programme lui-même peut être vu comme scanl . map reactToEvents $ zipN <eventStreams>du programme, oùN est égal au nombre d'observables jamais pris en compte par le programme.

Travailler avec des observables FRP permet de récupérer la composabilité car les événements d'un flux sont ordonnés dans le temps. La raison en est que l'abstraction du flux d'événements permet de visualiser tous les observables sous forme de boîtes noires. En fin de compte, la combinaison de flux d'événements à l'aide d'opérateurs redonne un ordre local lors de l'exécution. Cela m'oblige à être beaucoup plus honnête sur les invariants sur lesquels mon programme s'appuie réellement, de la même manière que toutes les fonctions de Haskell doivent être référentiellement transparentes: si je veux extraire des données d'une autre partie de mon programme, je dois être explicite annonce un type approprié pour mes fonctions. (La monade IO, étant un langage spécifique au domaine pour écrire du code impur, contourne efficacement cela)


-22

La programmation fonctionnelle a peut-être évolué depuis que j'étais à l'université, mais si je me souviens, le point principal d'un système de programmation fonctionnelle était d'empêcher le programmeur de créer un «effet secondaire». Cependant, les utilisateurs achètent des logiciels en raison des effets secondaires qui sont créés, par exemple la mise à jour d'une interface utilisateur.


25
Je pense que vous avez mal compris le point: ce n'est pas que la programmation fonctionnelle n'a aucun effet extérieur sur le monde - cela rendrait tous les programmes entièrement inutiles! Au contraire, la programmation fonctionnelle vous permet de mettre l'IO en quarantaine afin que vous sachiez quels bits l'utilisent et quels bits ne l'utilisent pas.
Tikhon Jelvis

Whoooooooosh x20
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.