¿Es posible la programación funcional de la GUI? [cerrado]


404

Recientemente detecté el error FP (tratando de aprender Haskell), y me impresionó mucho lo que he visto hasta ahora (funciones de primera clase, evaluación perezosa y todas las demás ventajas). Todavía no soy un experto, pero ya he comenzado a encontrar que es más fácil razonar "funcionalmente" que imperativamente para algoritmos básicos (y estoy teniendo problemas para volver a donde tengo que hacerlo).

Sin embargo, la única área donde el FP actual parece fallar es la programación de la GUI. El enfoque de Haskell parece ser simplemente envolver los kits de herramientas de GUI imperativos (como GTK + o wxWidgets) y usar bloques "do" para simular un estilo imperativo. No he usado F #, pero entiendo que hace algo similar usando OOP con clases .NET. Obviamente, hay una buena razón para esto: la programación actual de la GUI se trata de E / S y efectos secundarios, por lo que la programación puramente funcional no es posible con la mayoría de los marcos actuales.

Mi pregunta es, ¿es posible tener un enfoque funcional para la programación GUI? Tengo problemas para imaginar cómo se vería esto en la práctica. ¿Alguien sabe de algún marco, experimental o de otro tipo, que intente este tipo de cosas (o incluso cualquier marco diseñado desde cero para un lenguaje funcional)? ¿O es la solución simplemente usar un enfoque híbrido, con OOP para las partes de la GUI y FP para la lógica? (Solo pregunto por curiosidad: me encantaría pensar que FP es "el futuro", pero la programación de la GUI parece un gran agujero para llenar).


77
Después de mirar las GUI en Common Lisp y OCaml, diría que, lo más probable, es la pereza de Haskell lo que está causando el problema.
new123456

55
@ new123456 Common Lisp no es un lenguaje funcional, funciona con datos mutables y abarca los efectos secundarios
Electric Coffee

3
@ElectricCoffee Lisp es un lenguaje extremadamente flexible capaz de usarse en muchos estilos diferentes, y muchas personas eligen usar Lisp en un estilo funcional.
chrismamo1

8
Desde mi experiencia (aunque todavía estoy tratando de creer en ello y aprendiendo más), FRP realmente alcanza su límite con la programación GUI; es agradable y elegante para el 80% de los casos de uso, pero los widgets ricos requieren un control muy preciso de su estado interno (por ejemplo, cuadros combinados de búsqueda, etc.) y FRP simplemente se interpone en el camino. El imperativo no siempre es malo; tratar de minimizar la cantidad de código imperativo es bueno pero eliminar el 100%? Todavía no lo he visto funcionar para el desarrollo no trivial de la interfaz de usuario.
AlexG

8
@ElectricCoffee "Common Lisp no es un lenguaje funcional". Lisp es la madre de todos los lenguajes funcionales. Quieres decir que Lisp no es puro.
Jon Harrop

Respuestas:


184

El enfoque de Haskell parece ser simplemente envolver los kits de herramientas de GUI imperativos (como GTK + o wxWidgets) y usar bloques "do" para simular un estilo imperativo

Ese no es realmente el "enfoque de Haskell", así es como se vincula a los kits de herramientas de GUI imperativos más directamente, a través de una interfaz imperativa. Haskell simplemente tiene enlaces bastante prominentes.

Existen varios enfoques moderadamente maduros, o más experimentales, puramente funcionales / declarativos para las GUI, principalmente en Haskell, y principalmente utilizando programación funcional reactiva.

Algunos ejemplos son:

Para aquellos de ustedes que no están familiarizados con Haskell, Flapjax, http://www.flapjax-lang.org/ es una implementación de programación funcional reactiva sobre JavaScript.


32
Consulte el artículo de Conal Elliott sobre la fruta para obtener una descripción excelente y detallada de la técnica y las decisiones: conal.net/papers/genuinely-functional-guis.pdf He estado haciendo programación GUI puramente funcional en este estilo durante algunos meses. . Me ENCANTA, es un alivio tan agradable del infierno de espagueti de la programación imperativa de la interfaz de usuario, que parece ser peor a este respecto que la mayoría de la programación imperativa.
luqui

44
Estoy 100% de acuerdo con esto. Para que quede claro como el cristal: la razón por la que a menudo se usan los kits de herramientas de GUI existentes es porque existen. La razón por la cual las interfaces con ellos tienden a ser imperativas e impuras es porque los juegos de herramientas tienden a ser imperativos e impuros. La razón por la cual los juegos de herramientas tienden a ser imperativos e impuros es porque los sistemas operativos de los que dependen tienden a ser imperativos e impuros. Sin embargo, no hay nada fundamental que requiera que ninguno de estos sea impuro: hay enlaces funcionales para esos juegos de herramientas, hay juegos de herramientas funcionales, incluso hay sistemas operativos funcionales.
Jörg W Mittag

16
Todo es solo una cuestión de pereza. (Mal juego de palabras).
Jörg W Mittag el

10
Algún día, todo el diseño de la GUI se implementará a través de WYSIWYG, con la lógica implementada funcionalmente. Esta es mi predicción.
BlueRaja - Danny Pflughoeft

24
El papel que menciona luqui parece estar muerto. Sin embargo, hay un enlace de trabajo en el sitio de Conal Elliott: conal.net/papers/genuinely-functional-guis.pdf
aganders3

74

Mi pregunta es, ¿es posible tener un enfoque funcional para la programación GUI?

Las palabras clave que está buscando son "programación reactiva funcional" (FRP).

Conal Elliott y algunos otros han hecho un poco de industria artesanal al tratar de encontrar la abstracción correcta para FRP. Hay varias implementaciones de conceptos FRP en Haskell.

Puede considerar comenzar con el documento más reciente de "Programación funcional y reactiva funcional Push-Pull" de Conal , pero hay varias otras implementaciones (más antiguas), algunas vinculadas desde el sitio haskell.org . Conal tiene una habilidad especial para cubrir todo el dominio, y su artículo se puede leer sin referencia a lo que vino antes.

Para tener una idea de cómo se puede usar este enfoque para el desarrollo de la GUI, es posible que desee ver Fudgets , que aunque se está haciendo un poco largo en estos días, diseñado a mediados de los 90, presenta un enfoque FRP sólido al diseño GUI.


Me gustaría agregar el aumento del uso de "Extensiones Reactivas" (Bibliotecas FRP; sin embargo, no FP), que fue originalmente escrito para C # y luego portado a Java (RxJava) y JavaScript (RxJS) y varios idiomas. Echa un vistazo a reactivex.io En este momento, Angular 2 hace un amplio uso de RxJS.
srph

63

Windows Presentation Foundation es una prueba de que el enfoque funcional funciona muy bien para la programación GUI. Tiene muchos aspectos funcionales y el código WPF "bueno" (búsqueda del patrón MVVM) enfatiza el enfoque funcional sobre el imperativo. Valientemente puedo afirmar que WPF es el kit de herramientas GUI funcional más exitoso del mundo real :-)

WPF describe la interfaz de usuario en XAML (aunque también puede reescribirla para que parezca funcionalmente C # o F #), por lo que para crear alguna interfaz de usuario, escriba:

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

Además, WPF también le permite describir de manera declarativa animaciones y reacciones a eventos usando otro conjunto de etiquetas declarativas (nuevamente, lo mismo puede escribirse como código C # / F #):

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

De hecho, creo que WPF tiene muchas cosas en común con el FRP de Haskell (aunque creo que los diseñadores de WPF no sabían sobre FRP y es un poco desafortunado; a veces, WPF se siente un poco extraño y poco claro si está utilizando el funcional punto de vista).


12
Si bien XAML es de naturaleza muy declarativa, ¿MVVM realmente fomenta un estilo funcional de programación? Toda la noción de un modelo de vista, cuyo trabajo es rastrear el estado de la vista (e implementa una interfaz llamada INotifyPropertyChangedde todas las cosas), me parece antitética a FP. Definitivamente no soy un experto en FP, y tal vez me estoy enfocando demasiado en el aspecto de inmutabilidad en comparación con el aspecto declarativo, pero tengo problemas para ver cómo el patrón MVVM (como se usa normalmente) es un ejemplo de FP.
devuxer

1
@devuxer Yo diría que sí. No creo que nadie realmente use FP para un código inmutable estricto. En cambio, usted decide dónde están sus límites de mutabilidad y trabaja inmutable en todos los demás niveles; en este caso, todos pueden asumir que el estado es inmutable, excepto por esa pequeña parte que en realidad muta el estado. Es similar a cómo funciona HTML: sí, tienes el DOM inmutable, pero cada vez que navegas, aún tienes que construir uno nuevo. INotifyPropertyChangedes solo una función de actualización que pasa a donde sea que necesite manejar las actualizaciones de la GUI: es una corrección de latencia.
Luaan

3
Steven Pemberton escribió 2 grandes publicaciones en F # y WPF, sus pensamientos sobre el desarrollo de WPF con F # hacia el final de la segunda publicación se suman a esta discusión. Otros dos ejemplos que también me intrigaron fueron el uso de un controlador funcional en MVVM controlado por eventos y el uso de uniones discriminadas y recursividad para construir una interfaz simple en la demostración de controles de WPF realizada por Flying Frog Consultancy.
Funk

29

De hecho, diría que la programación funcional (F #) es una herramienta mucho mejor para la programación de la interfaz de usuario que, por ejemplo, C #. Solo necesita pensar en el problema un poco diferente.

Discuto este tema en mi libro de programación funcional en el Capítulo 16, pero hay un extracto gratuito disponible , que muestra (en mi humilde opinión) el patrón más interesante que puede usar en F #. Supongamos que desea implementar el dibujo de rectángulos (el usuario presiona el botón, mueve el mouse y suelta el botón). En F #, puedes escribir algo como esto:

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 }

Este es un enfoque muy imperativo (en el estilo F # pragmático habitual), pero evita el uso de estado mutable para almacenar el estado actual del dibujo y para almacenar la ubicación inicial. Sin embargo, puede hacerse aún más funcional, escribí una biblioteca que lo hace como parte de mi tesis de maestría, que debería estar disponible en mi blog en los próximos días.

La programación reactiva funcional es un enfoque más funcional, pero me resulta un poco más difícil de usar ya que se basa en características de Haskell bastante avanzadas (como las flechas). Sin embargo, es muy elegante en una gran cantidad de casos. Su limitación es que no puede codificar fácilmente una máquina de estados (que es un modelo mental útil para programas reactivos). Esto es muy fácil usando la técnica F # anterior.


77
+1 Esto refleja nuestra experiencia, después de haber escrito varias GUI de producción en F # usando bibliotecas combinatorias y IObservable.
Jon Harrop

¿Ha cambiado el comentario sobre FRP desde la introducción de extensiones reactivas a la biblioteca .NET?
Fsharp Pete

1
Aquí hay algunas investigaciones sobre Arrowized FRP y cómo los efectos y la mutación pueden integrarse dentro de Arrowized FRP sin infringir las leyes: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (por cierto, la mayoría de las bibliotecas de FRP usan mónadas o incluso Solicitantes, por lo que no es correcto que se requieran flechas).
Erik Kaplun

17

Ya sea que esté en un lenguaje híbrido funcional / OO como F # u OCaml, o en un lenguaje puramente funcional como Haskell, donde los efectos secundarios se relegan a la mónada IO, en su mayoría se requiere una tonelada de trabajo para administrar una GUI es mucho más como un "efecto secundario" que como un algoritmo puramente funcional.

Dicho esto, ha habido una investigación realmente sólida puesta en GUI funcionales . Incluso hay algunos (en su mayoría) kits de herramientas funcionales como Fudgets o FranTk .


66
Enlace "GUIs funcionales" roto :( en caché: webcache.googleusercontent.com/search?q=cache:http://…
Dan Burton


12

Una de las ideas que abre la mente detrás de la Programación Reactiva Funcional es tener una función de manejo de eventos que produzca AMBAS reacciones a los eventos Y la próxima función de manejo de eventos. Así, un sistema en evolución se representa como una secuencia de funciones de manejo de eventos.

Para mí, aprender sobre Yampa se convirtió en un punto crucial para lograr que las funciones de producción de funciones funcionen correctamente. Hay algunos buenos documentos sobre Yampa. Recomiendo The Yampa Arcade:

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf (diapositivas, PDF) http://www.cs.nott.ac.uk/~nhn/Publications/hw2003. pdf (artículo completo, PDF)

Hay una página wiki en Yampa en Haskell.org

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

Página de inicio original de Yampa:

http://www.haskell.org/yampa (lamentablemente está roto en este momento)


1
Ese enlace está roto por mucho tiempo. Prueba esto Yampa
CDR

7

Desde que se hizo esta pregunta por primera vez, Elm ha hecho que la programación reactiva funcional sea un poco más convencional.

Sugiero consultarlo en http://elm-lang.org , que también tiene algunos tutoriales interactivos realmente excelentes sobre cómo hacer una GUI completamente funcional en el navegador.

Le permite crear interfaces gráficas de usuario (GUI) completamente funcionales en las que el código que necesita proporcionarse solo consta de funciones puras. Personalmente, me pareció mucho más fácil entrar que los diversos marcos de Haskell GUI.


Aquí está la tesis original de FRP detrás de Elm . Pero también desde mayo de 2016 Elm ya no es un lenguaje FRP .
icc97

6

La charla de Elliot sobre FRP se puede encontrar aquí .

Además, no es realmente una respuesta, sino un comentario y algunas reflexiones : de alguna manera, el término "GUI funcional" se parece un poco a un oxímoron (pureza e IO en el mismo término).

Pero mi vago entendimiento es que la programación funcional de la GUI se trata de definir declarativamente una función dependiente del tiempo que toma la entrada del usuario (real) dependiente del tiempo y produce una salida GUI dependiente del tiempo.

En otras palabras, esta función se define como una ecuación diferencial de forma declarativa, en lugar de mediante un algoritmo que usa imperativamente un estado mutable.

Entonces, en FP convencional, uno usa funciones independientes del tiempo, mientras que en FRP uno usa funciones dependientes del tiempo como bloques de construcción para describir un programa.

Pensemos en simular una pelota en un resorte con el que el usuario puede interactuar. La posición de la pelota es la salida gráfica (en la pantalla), el usuario que empuja la pelota es presionar una tecla (entrada).

La descripción de este programa de simulación en FRP (según mi entendimiento) se realiza mediante una sola ecuación diferencial (declarativamente): aceleración * masa = - estiramiento del resorte * constante del resorte + Fuerza ejercida por el usuario.

Aquí hay un video sobre ELM que ilustra este punto de vista.


5

A partir de 2016, hay varios marcos FRP más maduros para Haskell, como Sodium y Reflex (pero también Netwire).

El libro de Manning sobre Programación Reactiva Funcional muestra la versión Java de Sodium, para ejemplos de trabajo, e ilustra cómo se comporta y escala una base de código FRP GUI en comparación con los enfoques imperativos y basados ​​en actores.

También hay un documento reciente sobre Arrowized FRP y la posibilidad de incorporar efectos secundarios, IO y mutación en un entorno de FRP puro y respetuoso de la ley: http://haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formatted-dissertation.pdf .

También vale la pena señalar que los marcos de JavaScript como ReactJS y Angular y muchos otros ya están utilizando un FRP o un enfoque funcional para lograr componentes de GUI escalables y componibles.


El sodio ha quedado en desuso a favor del plátano reactivo de acuerdo con el archivo Léame de Github de Sodium
mac10688

4

Los lenguajes de marcado como XUL le permiten crear una GUI de forma declarativa.


3

Para abordar esto publiqué algunas ideas mías al usar 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 /

También estoy planeando hacer un video tutorial para terminar la serie y mostrar cómo F # puede contribuir en la programación UX.

Solo estoy hablando en el contexto de F # aquí.

-Fahad


2

Todas estas otras respuestas se basan en la programación funcional, pero toman muchas de sus propias decisiones de diseño. Una biblioteca que se construye básicamente a partir de funciones y tipos de datos abstractos simples es gloss. Aquí está el tipo para su playfunción desde la fuente

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

Como puede ver, funciona completamente al proporcionar funciones puras con tipos abstractos simples, con los que otras bibliotecas lo ayudan.


1

La innovación más evidente notada por las personas nuevas en Haskell es que existe una separación entre el mundo impuro que se ocupa de comunicarse con el mundo exterior y el mundo puro de la computación y los algoritmos. Una pregunta frecuente para principiantes es "¿Cómo puedo deshacerme IO, es decir, convertirme IO aen a?" La forma de hacerlo es usar mónadas (u otras abstracciones) para escribir código que realice IO y efectos de cadenas. Este código recopila datos del mundo exterior, crea un modelo de él, realiza algunos cálculos, posiblemente empleando código puro, y genera el resultado.

En lo que respecta al modelo anterior, no veo nada terriblemente malo en manipular las GUI en la IOmónada. El mayor problema que surge de este estilo es que los módulos ya no son componibles, es decir, pierdo la mayor parte de mi conocimiento sobre el orden de ejecución global de las declaraciones en mi programa. Para recuperarlo, tengo que aplicar un razonamiento similar al del código GUI concurrente e imperativo. Mientras tanto, para el código impuro, no GUI, el orden de ejecución es obvio debido a la definición del operador de la IOmónada >==(al menos siempre que haya un solo hilo). Para el código puro, no importa en absoluto, excepto en casos de esquina para aumentar el rendimiento o evitar evaluaciones que resulten .

La mayor diferencia filosófica entre la consola y el IO gráfico es que los programas que implementan los primeros generalmente se escriben en estilo sincrónico. Esto es posible porque hay (dejando de lado las señales y otros descriptores de archivos abiertos) solo una fuente de eventos: el flujo de bytes comúnmente llamado stdin. Sin embargo, las GUI son inherentemente asíncronas y tienen que reaccionar a los eventos del teclado y los clics del mouse.

Una filosofía popular de hacer IO asíncrona de manera funcional se llama Programación Reactiva Funcional (FRP). Recientemente obtuvo mucha tracción en lenguajes impuros y no funcionales gracias a bibliotecas como ReactiveX y frameworks como Elm. En pocas palabras, es como ver elementos de la GUI y otras cosas (como archivos, relojes, alarmas, teclado, mouse) como orígenes de eventos, llamados "observables", que emiten flujos de eventos. Estos eventos se combinan mediante operadores familiares, tales como map, foldl, zip, filter, concat, join, etc, para producir nuevas fuentes. Esto es útil porque el estado del programa en sí mismo puede verse como parte scanl . map reactToEvents $ zipN <eventStreams>del programa, donde Nes igual al número de observables que el programa haya considerado.

Trabajar con observables de FRP hace posible recuperar la componibilidad porque los eventos en una secuencia se ordenan a tiempo. La razón es que la abstracción del flujo de eventos hace posible ver todos los observables como cuadros negros. En última instancia, la combinación de flujos de eventos utilizando operadores devuelve algunos pedidos locales en la ejecución. Esto me obliga a ser mucho más honesto acerca de los invariantes en los que realmente confía mi programa, de manera similar a la forma en que todas las funciones en Haskell tienen que ser referencialmente transparentes: si quiero obtener datos de otra parte de mi programa, tengo que ser explícito anuncio declara un tipo apropiado para mis funciones. (La mónada IO, al ser un lenguaje específico de dominio para escribir código impuro, efectivamente lo evita)


-22

La programación funcional puede haber pasado de cuando estaba en la universidad, pero como recuerdo, el punto principal de un sistema de programación funcional era evitar que el programador creara un "efecto secundario". Sin embargo, los usuarios compran software debido a los efectos secundarios que se crean, por ejemplo, actualizar una IU.


25
Creo que entendió mal el punto: no es que la programación funcional no tenga un efecto externo en el mundo, ¡eso haría que todos los programas sean completamente inútiles! Más bien, la programación funcional le permite poner en cuarentena el IO para que sepa qué bits lo usan y cuáles no.
Tikhon Jelvis

Whoooooooosh x20
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.