É possível a programação funcional da GUI? [fechadas]


404

Recentemente, peguei o bug do FP (tentando aprender Haskell) e fiquei realmente impressionado com o que vi até agora (funções de primeira classe, avaliação preguiçosa e todas as outras vantagens). Ainda não sou especialista, mas já comecei a achar mais fácil raciocinar "funcionalmente" do que imperativamente para algoritmos básicos (e estou tendo problemas para voltar ao que preciso).

A única área em que o atual FP parece falhar é a programação da GUI. A abordagem Haskell parece ser apenas agrupar kits de ferramentas da GUI imperativos (como GTK + ou wxWidgets) e usar blocos "do" para simular um estilo imperativo. Eu não usei o F #, mas meu entendimento é que ele faz algo semelhante usando OOP com classes .NET. Obviamente, há uma boa razão para isso - a programação da GUI atual é sobre E / S e efeitos colaterais; portanto, a programação puramente funcional não é possível com a maioria das estruturas atuais.

Minha pergunta é: é possível ter uma abordagem funcional para a programação da GUI? Estou tendo problemas para imaginar como isso seria na prática. Alguém sabe de estruturas, experimentais ou não, que tentam esse tipo de coisa (ou mesmo estruturas projetadas desde o início para uma linguagem funcional)? Ou a solução é usar apenas uma abordagem híbrida, com OOP para as partes da GUI e FP para a lógica? (Estou apenas perguntando por curiosidade - adoraria pensar que o FP é "o futuro", mas a programação da GUI parece um buraco muito grande para preencher.)


7
Tendo examinado as GUIs no Common Lisp e no OCaml, eu diria que, mais provavelmente, a preguiça de Haskell que está causando o problema.
new123456

5
@ new123456 Lisp comum não é uma linguagem funcional, porém, ele trabalha com dados mutáveis e abraça efeitos colaterais
café eléctrica

3
@ElectricCoffee Lisp é uma linguagem extremamente flexível capaz de ser usada em muitos estilos diferentes, e muitas pessoas optam por usar o Lisp em um estilo funcional.
chrismamo1

8
Pela minha experiência (embora eu ainda esteja tentando acreditar nisso e aprendendo mais), o FRP realmente atinge seu limite com a programação da GUI; é agradável e elegante para 80% dos casos de uso, mas widgets avançados exigem um controle muito preciso de seu estado interno (por exemplo, caixas de combinação de pesquisa, etc.) e o FRP apenas atrapalha. Imperativo nem sempre é mau; tentar minimizar a quantidade de código imperativo é bom, mas remover 100% dele? Ainda não o vi funcionar para o desenvolvimento não trivial da interface do usuário.
AlexG

8
@ElectricCoffee "O Lisp comum não é uma linguagem funcional". Lisp é a mãe de todas as linguagens funcionais. Você quer dizer que Lisp não é puro.
Jon Harrop 22/02

Respostas:


184

A abordagem Haskell parece ser apenas agrupar kits de ferramentas da GUI imperativos (como GTK + ou wxWidgets) e usar blocos "do" para simular um estilo imperativo

Essa não é realmente a "abordagem Haskell" - é assim que você se liga aos kits de ferramentas da GUI imperativos mais diretamente - através de uma interface imperativa. Por acaso, Haskell tem ligações bastante proeminentes.

Existem várias abordagens puramente funcionais / declarativas moderadamente maduras ou mais experimentais para GUIs, principalmente em Haskell, e principalmente usando programação reativa funcional.

Alguns exemplos são:

Para aqueles que não estão familiarizados com Haskell, Flapjax, http://www.flapjax-lang.org/ é uma implementação de programação reativa funcional sobre JavaScript.


32
Veja o artigo de Conal Elliott sobre frutas para obter uma descrição detalhada e detalhada da técnica e das decisões: conal.net/papers/genuinely-functional-guis.pdf Estou fazendo programação GUI puramente funcional nesse estilo há alguns meses . Eu adoro isso, é um alívio agradável do inferno de espaguete da programação imperativa da interface do usuário, que parece ser pior a esse respeito do que a maioria da programação imperativa.
Luqui

44
Eu concordo 100% com isso. Para deixar bem claro: a razão pela qual os kits de ferramentas da GUI existentes são frequentemente usados ​​é porque eles existem. A razão pela qual as interfaces para eles tendem a ser imperativas e impuras é porque os kits de ferramentas tendem a ser imperativos e impuros. A razão pela qual os kits de ferramentas tendem a ser imperativos e impuros é porque os sistemas operacionais dos quais eles dependem tendem a ser imperativos e impuros. No entanto, não há nada que exija fundamentalmente que nada disso seja impuro: há ligações funcionais para esses kits de ferramentas, kits de ferramentas funcionais e até sistemas operacionais funcionais.
Jörg W Mittag

16
É tudo apenas uma questão de preguiça. (Bad pun pretendido.)
Jörg W Mittag

10
Algum dia, todo o design da GUI será implementado via WYSIWYG, com a lógica implementada funcionalmente. Esta é a minha previsão.
precisa saber é o seguinte

24
O artigo que luqui menciona parece estar morto. No entanto, existe um link de trabalho no site de Conal Elliott: conal.net/papers/genuinely-functional-guis.pdf
aganders3

74

Minha pergunta é: é possível ter uma abordagem funcional para a programação da GUI?

As palavras-chave que você está procurando são "programação reativa funcional" (FRP).

Conal Elliott e alguns outros criaram um pouco da indústria caseira ao tentar encontrar a abstração certa para o FRP. Existem várias implementações de conceitos de FRP em Haskell.

Você pode considerar começar com o artigo "Programação reativa funcional por push-pull" mais recente da Conal , mas existem várias outras implementações (mais antigas), algumas delas vinculadas no site haskell.org . Conal tem um talento especial para cobrir todo o domínio, e seu artigo pode ser lido sem referência ao que veio antes.

Para ter uma idéia de como essa abordagem pode ser usada para o desenvolvimento da GUI, convém examinar o Fudgets , que, embora esteja ficando um pouco longo nos dias de hoje, sendo projetado em meados dos anos 90, apresenta uma abordagem sólida de FRP ao design da GUI.


Eu gostaria de adicionar o aumento do uso de "Extensões reativas" (bibliotecas FRP; no entanto, não FP), que foi originalmente escrito para C # e depois transportado para Java (RxJava) e JavaScript (RxJS) e várias linguagens. No momento, o Angular 2 faz uso extensivo do RxJS.
srph 17/01/16

63

O Windows Presentation Foundation é uma prova de que a abordagem funcional funciona muito bem para a programação da GUI. Possui muitos aspectos funcionais e o código WPF "bom" (pesquisa pelo padrão MVVM) enfatiza a abordagem funcional sobre o imperativo. Eu poderia bravamente afirmar que o WPF é o kit de ferramentas GUI funcional do mundo real mais bem-sucedido :-)

O WPF descreve a interface do usuário em XAML (embora você possa reescrevê-la para aparência funcional em C # ou F # também), portanto, para criar alguma interface de usuário, escreva:

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

Além disso, o WPF também permite descrever declarativamente animações e reações a eventos usando outro conjunto de tags declarativas (novamente, a mesma coisa pode ser escrita como código C # / F #):

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

Na verdade, acho que o WPF tem muitas coisas em comum com o FRP de Haskell (embora eu acredite que os designers do WPF não sabiam sobre o FRP e seja um pouco lamentável - o WPF às vezes se sente um pouco estranho e pouco claro se você estiver usando o recurso funcional. ponto de vista).


12
Embora o XAML seja de natureza muito declarativa, o MVVM realmente incentiva um estilo funcional de programação? Toda a noção de um modelo de visão, cujo trabalho é rastrear o estado da visão (e implementa uma interface chamada INotifyPropertyChangedde todas as coisas), parece antitético ao FP para mim. Definitivamente, não sou especialista em FP, e talvez esteja focando muito no aspecto da imutabilidade em oposição ao aspecto declarativo, mas estou tendo problemas para ver como o padrão MVVM (como normalmente usado) é um exemplo de FP.
devuxer

11
@ Devuxer Eu diria que sim. Eu não acho que alguém usaria o FP de forma realista para um código imutável estrito. Em vez disso, você decide onde estão os seus limites de mutabilidade e trabalha imutável em todos os outros níveis - nesse caso, todos podem assumir que o estado é imutável, exceto a pequena parte que realmente muda o estado. É semelhante à forma como o HTML funciona - sim, você tem o DOM imutável, mas sempre que você navega, ainda precisa criar um novo. INotifyPropertyChangedé apenas uma função de atualização que você passa para onde quer que precise lidar com as atualizações da GUI - é uma correção de latência.
Luaan 13/07/2015

3
Steven Pemberton escreveu 2 ótimos posts sobre F # e WPF, seus pensamentos sobre o desenvolvimento do WPF com F # no final do segundo post contribuem para essa discussão. Outros dois exemplos que também me intrigaram foram o uso de um controlador funcional no MVVM orientado a eventos e o uso de uniões e recursões discriminadas para a construção de uma interface simples na demonstração de controles WPF da Flying Frog Consultancy.
Funk

29

Na verdade, eu diria que a programação funcional (F #) é uma ferramenta muito melhor para a programação da interface do usuário do que, por exemplo, o C #. Você só precisa pensar sobre o problema de maneira um pouco diferente.

Discuto esse tópico no meu livro de programação funcional no Capítulo 16, mas há um trecho gratuito disponível , que mostra (IMHO) o padrão mais interessante que você pode usar no F #. Digamos que você queira implementar o desenho de retângulos (o usuário aperta o botão, move o mouse e solta o botão). Em F #, você pode escrever algo como isto:

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 }

Essa é uma abordagem muito imperativa (no estilo F # pragmático usual), mas evita o uso de estado mutável para armazenar o estado atual do desenho e para armazenar a localização inicial. Pode ser ainda mais funcional: escrevi uma biblioteca que faz isso como parte da minha tese de mestrado, que deve estar disponível no meu blog nos próximos dias.

A Programação Reativa Funcional é uma abordagem mais funcional, mas acho um pouco mais difícil de usar, pois depende de recursos Haskell bastante avançados (como setas). No entanto, é muito elegante em um grande número de casos. Sua limitação é que você não pode codificar facilmente uma máquina de estado (que é um modelo mental útil para programas reativos). Isso é muito fácil usando a técnica F # acima.


7
+1 Isso reflete nossa experiência, tendo escrito várias GUIs de produção em F # usando bibliotecas combinadoras e IObservable.
precisa saber é o seguinte

O comentário sobre FRP mudou desde a introdução de extensões reativas na biblioteca .NET?
FSharp Pete

11
Aqui estão algumas pesquisas sobre FRP Arrowized e como os efeitos e mutações podem ser incorporados no FRP Arrowized sem violar as leis: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (mas a maioria das bibliotecas FRP usa Mônadas ou mesmo Candidatos, portanto, não é correto que as setas sejam necessárias).
Erik Kaplun

17

Quer você esteja em uma linguagem funcional / OO híbrida como F # ou OCaml, ou em uma linguagem puramente funcional como Haskell, onde os efeitos colaterais são relegados à mônada de IO, geralmente ocorre uma tonelada do trabalho necessário para gerenciar uma GUI é muito mais um "efeito colateral" do que um algoritmo puramente funcional.

Dito isto, houve algumas pesquisas realmente sólidas sobre GUIs funcionais . Existem até alguns kits de ferramentas funcionais (principalmente), como Fudgets ou FranTk .


6
link "GUIs funcionais" quebrado :( armazenado em cache: webcache.googleusercontent.com/search?q=cache:http://…
Dan Burton


12

Uma das idéias de abertura da mente por trás da Programação Reativa Funcional é ter uma função de manipulação de eventos produzindo AMBOS a reação a eventos E a próxima função de manipulação de eventos. Assim, um sistema em evolução é representado como uma sequência de funções de manipulação de eventos.

Para mim, aprender Yampa tornou-se um ponto crucial para entender adequadamente essa coisa de produzir funções. Existem alguns papéis interessantes sobre o Yampa. Eu recomendo The Yampa Arcade:

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

Existe uma página wiki no Yampa em Haskell.org

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

Página inicial original do Yampa:

http://www.haskell.org/yampa (infelizmente está quebrado no momento)


11
Esse link está quebrado por um longo tempo. Experimente este Yampa
CR

7

Desde que essa pergunta foi feita pela primeira vez, a programação reativa funcional tornou-se um pouco mais mainstream pela Elm.

Sugiro que verifique em http://elm-lang.org , que também possui alguns tutoriais interativos verdadeiramente excelentes sobre como criar uma GUI totalmente funcional no navegador.

Ele permite criar GUI totalmente funcionais, onde o código que você precisa fornecer consiste apenas em funções puras. Pessoalmente, achei muito mais fácil entrar do que as várias estruturas da GUI Haskell.



6

A palestra de Elliot sobre FRP pode ser encontrada aqui .

Além disso, não é realmente uma resposta, mas uma observação e alguns pensamentos : de alguma forma, o termo "GUI funcional" parece um pouco com um oxímoro (pureza e IO no mesmo termo).

Mas meu entendimento vago é que a programação funcional da GUI é sobre definir declarativamente uma função dependente do tempo que recebe a entrada do usuário (real) dependente do tempo e produz saída GUI dependente do tempo.

Em outras palavras, essa função é definida declarativamente como uma equação diferencial, em vez de um algoritmo usar imperativamente o estado mutável.

Assim, no FP convencional, utiliza-se funções independentes do tempo, enquanto no FRP, utiliza-se funções dependentes do tempo como blocos de construção para descrever um programa.

Vamos pensar em simular uma bola em uma mola com a qual o usuário possa interagir. A posição da bola é a saída gráfica (na tela), o usuário pressionando a bola pressiona a tecla (entrada).

A descrição deste programa de simulação em FRP (de acordo com o meu entendimento) é feita por uma única equação diferencial (declarativa): aceleração * massa = - extensão da mola * constante da mola + Força exercida pelo usuário.

Aqui está um vídeo no ELM que ilustra esse ponto de vista.


5

A partir de 2016, existem várias estruturas FRP relativamente maduras para Haskell, como Sodium e Reflex (mas também Netwire).

O livro Manning sobre Programação Reativa Funcional mostra a versão Java do sódio, para exemplos de trabalho, e ilustra como uma base de código da FRP GUI se comporta e escala em comparação com abordagens imperativas e baseadas em atores.

Há também um artigo recente sobre FRP Arrowized e a perspectiva de incorporar efeitos colaterais, IO e mutações em um ambiente de FRP puro e cumpridor da lei: http://haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formatted-dissertation.pdf .

Também digno de nota é que as estruturas JavaScript como ReactJS e Angular e muitas outras já estão ou estão adotando uma abordagem FRP ou funcional para obter componentes de GUI escaláveis ​​e composíveis.


De sódio foi preterido em favor de bananeira reativa de acordo com de sódio github readme
mac10688


3

Para resolver isso, postei algumas idéias minhas usando 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 /

Também estou planejando fazer um tutorial em vídeo para concluir a série e mostrar como o F # pode contribuir na programação de UX.

Eu só estou falando no contexto de F # aqui.

-Fahad


2

Todas essas outras respostas são criadas com base na programação funcional, mas tomam muitas de suas próprias decisões de design. Uma biblioteca que é construída basicamente inteiramente a partir de funções e tipos de dados abstratos simples é gloss. Aqui está o tipo para sua playfunção da 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 ()

Como você pode ver, ele funciona inteiramente fornecendo funções puras com tipos abstratos simples, com os quais outras bibliotecas o ajudam.


1

A inovação mais aparente observada por pessoas novas em Haskell é que existe uma separação entre o mundo impuro que se preocupa em se comunicar com o mundo exterior e o mundo puro da computação e algoritmos. Uma pergunta freqüente para iniciantes é "Como posso me livrar IO, ou seja, converter IO aem a?" O caminho para isso é usar mônadas (ou outras abstrações) para escrever código que execute efeitos de IO e encadeamentos. Esse código reúne dados do mundo exterior, cria um modelo, faz algumas computações, possivelmente empregando código puro, e gera o resultado.

No que diz respeito ao modelo acima, não vejo nada de errado em manipular GUIs na IOmônada. O maior problema que surge desse estilo é que os módulos não são mais compostáveis, ou seja, perco a maior parte do meu conhecimento sobre a ordem de execução global das instruções no meu programa. Para recuperá-lo, tenho de aplicar um raciocínio semelhante ao do código da GUI concorrente e imperativo. Enquanto isso, para um código não-GUI impuro, a ordem de execução é óbvia por causa da definição IOdo >==operador da mônada (pelo menos enquanto houver apenas um encadeamento). Para código puro, isso não importa, exceto em casos extremos para aumentar o desempenho ou evitar avaliações resultantes .

A maior diferença filosófica entre console e E / S gráfica é que os programas que implementam o primeiro são geralmente escritos em estilo síncrono. Isso é possível porque existe (deixando de lado os sinais e outros descritores de arquivos abertos) apenas uma fonte de eventos: o fluxo de bytes normalmente chamado stdin. As GUIs são inerentemente assíncronas e precisam reagir a eventos do teclado e cliques do mouse.

Uma filosofia popular de executar IO assíncrona de uma maneira funcional é chamada de Programação Reativa Funcional (FRP). Recentemente, houve muita tração em linguagens impuras e não funcionais, graças a bibliotecas como o ReactiveX e estruturas como o Elm. Em poucas palavras, é como visualizar elementos da GUI e outras coisas (como arquivos, relógios, alarmes, teclado, mouse) como fontes de eventos, chamadas "observáveis", que emitem fluxos de eventos. Estes eventos são combinados usando operadores conhecidos, como map, foldl, zip, filter, concat, join, etc., para produzir novos fluxos. Isso é útil porque o próprio estado do programa pode ser visto como scanl . map reactToEvents $ zipN <eventStreams>do programa, onde Né igual ao número de observáveis ​​já considerados pelo programa.

Trabalhar com observáveis ​​de FRP torna possível recuperar a composição porque os eventos em um fluxo são ordenados a tempo. O motivo é que a abstração do fluxo de eventos torna possível visualizar todos os observáveis ​​como caixas pretas. Por fim, a combinação de fluxos de eventos usando operadores devolve algumas ordens locais na execução. Isso me obriga a ser muito mais honesto em relação aos invariantes em que meu programa realmente se baseia, semelhante à maneira como todas as funções no Haskell precisam ser referencialmente transparentes: se eu quiser extrair dados de outra parte do meu programa, preciso ser explícito. O anúncio declara um tipo apropriado para minhas funções. (A mônada de IO, sendo uma linguagem específica de domínio para escrever código impuro, evita isso efetivamente)


-22

A programação funcional pode ter mudado desde quando eu estava na universidade, mas pelo que me lembro, o ponto principal de um sistema de programação funcional era impedir o programador de criar qualquer "efeito colateral". No entanto, os usuários compram software devido aos efeitos colaterais criados, por exemplo, atualizando uma interface do usuário.


25
Acho que você não entendeu o ponto: não é que a programação funcional não tenha efeito externo no mundo - isso tornaria todos os programas totalmente inúteis! Em vez disso, a programação funcional permite colocar o IO em quarentena para que você saiba quais bits o utilizam e quais não.
Tikhon Jelvis

X20 Whoooooooosh
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.