web-dev-qa-db-fra.com

La programmation d'interface graphique fonctionnelle est-elle possible?

J'ai récemment attrapé le bogue FP (j'essaie 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 qu'il est plus facile de raisonner "fonctionnellement" qu'impérieusement pour les algorithmes de base (et j'ai du mal à retourner là où je dois le faire).

Le seul domaine où FP actuel semble tomber à plat, cependant, est la programmation graphique. L’approche de Haskell semble consister simplement à emballer des kits d’interface graphique 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 d'après ce que j'ai compris, il utilise quelque chose de similaire avec OOP avec les classes .NET. De toute évidence, il y a une bonne raison à cela: la programmation actuelle de l'interface graphique est entièrement axée sur IO et les effets secondaires, de sorte qu'une programmation purement fonctionnelle n'est pas possible avec la plupart des frameworks actuels.

Ma question est la suivante: est-il possible d'avoir une approche fonctionnelle de la programmation d'interface graphique? J'ai du mal à imaginer à quoi cela ressemblerait dans la pratique. Est-ce que quelqu'un connaît des frameworks, expérimentaux ou autres, qui essayent ce genre de chose (ou même des frameworks conçus à la base pour un langage fonctionnel)? Ou bien la solution consiste-t-elle simplement à utiliser une approche hybride, avec OOP pour les parties de l'interface graphique et FP pour la logique? (Je demande simplement par curiosité - j'aimerais beaucoup penser que FP est "l'avenir", mais la programmation d'interface graphique semble être un assez gros trou à combler.)

393
shosti

L’approche de Haskell semble être de simplement emballer des kits d’outils d’interface graphique 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 d'interface graphique impératifs - via une interface impérative. Haskell a juste des liaisons assez importantes.

Il existe plusieurs approches modérément matures, ou plus expérimentales, purement fonctionnelles/déclaratives d’interface graphique, principalement en Haskell, et utilisant principalement une programmation réactive fonctionnelle.

Certains exemples sont:

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

181
Don Stewart

Ma question est la suivante: est-il possible d'avoir une approche fonctionnelle de la programmation d'interface graphique?

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

Conal Elliott et quelques autres ont fait de l’industrie artisanale un peu pour tenter de trouver la bonne abstraction pour FRP. Il y a plusieurs implémentations des concepts FRP dans Haskell.

Vous pourriez envisager de commencer par le plus récent document de Conal "Programmation fonctionnelle réactive push-pull" , mais il existe plusieurs autres (plus anciennes) mises en œuvre, dont certaines sont liées à la Site haskell.org . Conal a le don de couvrir l’ensemble du domaine et son article peut être lu sans référence à ce qui était auparavant.

Pour avoir une idée de la façon dont cette approche peut être utilisée pour le développement d'interface graphique, vous pouvez vous pencher sur Fudgets , qui, même si elle est un peu longue La dent de nos jours, conçue au milieu des années 90, présente une approche solide en PRF de la conception d'interface graphique.

72
Edward KMETT

Windows Presentation Foundation prouve que l'approche fonctionnelle fonctionne très bien pour la programmation par interface graphique. Il comporte de nombreux aspects fonctionnels et le "bon" code WPF (recherche de modèle MVVM) souligne l’approche fonctionnelle avant l’impératif. Je pourrais courageusement prétendre que WPF est la boîte à outils de l'interface graphique fonctionnelle la plus aboutie du monde réel :-)

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

<!-- 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 tant que 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 points communs avec le PRF de Haskell (bien que je pense que les concepteurs de WPF ne connaissaient pas le PRF et que c'est un peu fâcheux - WPF se sent parfois un peu bizarre et incertain si vous utilisez le fonctionnel point de vue).

61
Tomas Petricek

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

Je discute de ce sujet dans ma programmation fonctionnelle livre au chapitre 16, mais il existe un extrait gratuit disponible , qui montre (à mon humble avis) le motif le plus intéressant que vous pouvez utiliser en fa # . Supposons que vous souhaitiez implémenter un 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 }

C'est une approche très impérative (dans le style F # pragmatique habituel), 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 le fait dans le cadre de ma thèse de maîtrise, qui devrait être disponible sur mon blog dans les 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 assez avancées de Haskell (telles que les flèches). Cependant, il est très élégant dans un grand nombre de cas. Sa limite est qu’il est difficile d’encoder une machine à états (qui est un modèle mental utile pour les programmes réactifs). Ceci est très facile en utilisant la technique F # ci-dessus.

27
Tomas Petricek

Que vous soyez dans un langage hybride fonctionnel/OO tel que F # ou OCaml, ou dans un langage purement fonctionnel tel que Haskell où les effets secondaires sont relégués au monade IO, c'est principalement le cas où une tonne de travail requise 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é effectuées dans interfaces graphiques fonctionnelles . Il existe même des kits (principalement) fonctionnels tels que Fudgets ou FranTk .

17
sblom

Vous pouvez voir la série de Don Syme sur F # où il a créé une interface graphique. le lien suivant est à la troisième partie de la série (vous pouvez relier à partir de là aux deux autres parties).

Utiliser F # pour le développement de WPF serait un paradigme d'interface graphique très intéressant ...

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

15
Kevin Won

L’une des idées qui ouvrent l’esprit à la base de la programmation réactive fonctionnelle 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é par une séquence de fonctions de gestion d'événements.

Pour moi, apprendre le Yampa est devenu un élément crucial pour bien comprendre le fonctionnement des fonctions productrices. Il y a quelques beaux papiers sur le 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 originale de Yampa:

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

12
Boris Berkgaut

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

Je suggère de le vérifier sur le site http://Elm-lang.org , qui propose également des didacticiels interactifs vraiment excellents sur la création d'une interface graphique entièrement fonctionnelle dans le navigateur.

Il vous permet de créer des interfaces graphiques entièrement fonctionnelles dans lesquelles le code que vous devez vous fournir vous-même ne contient que des fonctions pures. Personnellement, j’ai trouvé qu’il était beaucoup plus facile d’entrer dans les divers cadres de l’interface graphique Haskell.

6
saolof

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

En outre, pas vraiment une réponse, mais une remarque et quelques réflexions : le terme "interface graphique fonctionnelle" 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 d'interface graphique dépendante du temps.

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

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

Pensons à la simulation d’une balle sur un ressort avec laquelle l’utilisateur peut interagir. La position de la balle est la sortie graphique (sur l'écran), l'utilisateur appuyant sur la balle est une pression de touche (entrée).

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

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

6
jhegedus

À compter de 2016, il existe plusieurs autres cadres FRP relativement matures pour Haskell, tels que Sodium et Reflex (mais également Netwire).

Le livre de Manning sur la programmation réactive fonctionnelle présente la version Javade Sodium, à titre d’exemples concrets, et illustre le comportement et l’échelle d’une base de code de l’interface graphique utilisateur FRP par rapport à impératif et à acteur. approches fondées.

Il existe également un article récent sur le PRF Arrowized et la perspective d’incorporer des effets secondaires, IO et une mutation dans un cadre FRP pur respectueux des lois: http://haskell.cs.yale.edu/ wp-content/uploads/2015/10/dwc-yale-formated-dissertation.pdf .

Il convient également de noter que les infrastructures JavaScript telles que ReactJS et Angular et de nombreux autres sont déjà en train d'utiliser ou utilisent actuellement une approche PRF ou fonctionnelle pour la réalisation de composants d'interface graphique évolutifs et composables.

5
Erik Allik

Les langages de balisage tels que XUL vous permettent de construire une interface graphique de manière déclarative.

4
StackedCrooked

Pour résoudre ce problème, j’ai publié certaines de mes pensées concernant l’utilisation de F #,

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

Je prévois également de faire un didacticiel 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

4
Fahad

Toutes ces autres réponses sont construites sur la programmation fonctionnelle, mais prennent beaucoup de décisions en matière de conception. Une bibliothèque construite essentiellement à partir de fonctions et de types de données abstraits simples est gloss . Voici le type pour sa play fonction 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 constater, cela fonctionne entièrement en fournissant des fonctions pures avec des types abstraits simples, que d'autres bibliothèques vous aident.

2
PyRulez

L'innovation la plus apparente remarquée par les nouveaux venus chez Haskell est qu'il existe une séparation entre le monde impur qui se préoccupe de communiquer 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 de IO, c'est-à-dire, convertir IO a en a?" Le moyen d'y parvenir est d'utiliser des monades (ou d'autres abstractions) pour écrire du code effectuant des effets IO et des chaînes. Ce code rassemble des données du monde extérieur, en crée un modèle, effectue des calculs, éventuellement en utilisant du code pur, et affiche le résultat.

En ce qui concerne le modèle ci-dessus, je ne vois rien de mal à manipuler des interfaces graphiques dans le monad IO. 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 de mon programme. Pour le récupérer, je dois appliquer le même raisonnement que dans le code d'interface graphique impératif simultané. Pendant ce temps, pour les codes impurs et non graphiques, l'ordre d'exécution est évident à cause de la définition de l'opérateur IO monad >== (au moins tant qu'il n'y a qu'un seul thread). Pour le code pur, cela n'a pas d'importance, sauf dans les cas critiques d'augmenter les performances ou d'éviter les évaluations aboutissant à .

La plus grande différence philosophique entre la console et le graphique IO est que les programmes implémentant le premier sont généralement écrits dans un style synchrone. Cela est possible car il n’ya qu’une source d’événements (à part les signaux et les descripteurs de fichiers ouverts): le flux d’octets communément appelé stdin. Cependant, les interfaces graphiques sont intrinsèquement asynchrones et doivent réagir aux événements de clavier et aux clics de souris.

Une philosophie répandue de faire de manière fonctionnelle IO asynchrone est appelée Programmation Réactive Fonctionnelle (FRP). Il a récemment fait ses preuves 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 si vous visualisiez des éléments de l'interface graphique et d'autres éléments (fichiers, horloges, alarmes, clavier, souris, etc.) en tant que sources d'événements, appelées "observables", qui émettaient des flux d'événements. Ces événements sont combinés à l’aide d’opérateurs familiers tels que map, foldl, Zip, filter, concat, join, etc., pour produire 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 classés dans le temps. La raison en est que l’abstraction du flux d’événements permet de visualiser tous les éléments observables sous forme de boîtes noires. En fin de compte, la combinaison de flux d'événements à l'aide d'opérateurs rend un ordre local lors de l'exécution. Cela me force à ê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 transparentes de manière référentielle: si je veux extraire des données d'une autre partie de mon programme, je dois être explicite ad déclare un type approprié pour mes fonctions. (La monade IO, en tant que langage spécifique à un domaine pour l'écriture de code impur, contourne effectivement cela)

1
MauganRa