功能GUI编程是否可行?

时间:2010-04-20 05:35:31

标签: user-interface haskell f# functional-programming

我最近发现了FP错误(试图学习Haskell),到目前为止我已经看到了真正的印象(一流的功能,懒惰的评估和所有其他好东西)。我还不是专家,但是我已经开始发现在功能上比在基本算法上强制推理更容易(而且我很难回到我必须去的地方)。

然而,当前FP看起来平坦的一个领域是GUI编程。 Haskell方法似乎只是包装命令式GUI工具包(例如GTK +或wxWidgets)并使用“do”块来模拟命令式样式。我没有使用过F#,但我的理解是它使用OOP和.NET类做了类似的事情。显然,有一个很好的理由 - 当前的GUI编程完全是关于IO和副作用的,所以对于大多数当前的框架来说,纯函数式编程是不可能的。

我的问题是,是否可以使用GUI编程的功能方法?我无法想象在实践中这会是什么样子。有没有人知道尝试过这种事情的任何框架,无论是实验的还是其他的(或者甚至是为功能语言设计的任何框架)?或者只是使用混合方法的解决方案,其中OOP用于GUI部件,FP用于逻辑? (我只是想出于好奇心 - 我很想认为FP是“未来”,但GUI编程似乎是一个非常大的漏洞。)

15 个答案:

答案 0 :(得分:177)

  

Haskell方法似乎只是包装命令式GUI工具包(例如GTK +或wxWidgets)并使用“do”块来模拟命令式样式

这不是真正的“Haskell方法” - 这就是你最直接地通过命令式界面绑定到命令式GUI工具包的方式。 Haskell碰巧有相当突出的绑定。

有几种中等成熟或更实验的纯功能/声明式GUI方法,主要是在Haskell中,主要使用功能性反应式编程。

一些例子是:

对于那些不熟悉Haskell的人,Flapjax,http://www.flapjax-lang.org/是基于JavaScript的函数式反应式编程的实现。

答案 1 :(得分:70)

  

我的问题是,是否可以使用GUI编程的功能方法?

您正在寻找的关键词是“功能反应式编程”(FRP)。

Conal Elliott和其他一些人试图找到适合FRP的抽象方法,使得一个小屋行业变得更加困难。 Haskell中有几种FRP概念的实现。

您可以考虑从Conal的最新"Push-Pull Functional Reactive Programming"论文开始,但还有其他一些(较旧的)实现,其中一些与haskell.org site相关联。 Conal有覆盖整个领域的诀窍,他的论文可以在不参考之前的内容的情况下阅读。

为了了解这种方法如何用于GUI开发,您可能希望看一下Fudgets,虽然这些日子有点长,但是设计在90年代中期,确实为GUI设计提供了坚实的FRP方法。

答案 2 :(得分:61)

Windows Presentation Foundation 证明了功能方法非常适合GUI编程。它有许多功能方面和“好”的WPF代码(搜索MVVM模式)强调功能方法超过命令。我可以勇敢地声称WPF是最成功的实际功能GUI工具包: - )

WPF描述了XAML中的用户界面(虽然您可以将其重写为功能上看起来也是C#或F#),因此要创建一些用户界面,您可以编写:

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

此外,WPF还允许您使用另一组声明性标记以声明方式描述动画和对事件的反应(同样,可以将其写为C#/ F#代码):

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

事实上,我认为WPF与Haskell的FRP有许多共同之处(虽然我相信WPF设计师并不了解FRP,但有点不幸 - WPF有时感觉有点奇怪而且不清楚你是不是使用功能观点)。

答案 3 :(得分:27)

我实际上会说功能编程(F#)是比C#更好的用户界面编程工具。你只需要稍微考虑一下这个问题。

我在第16章的my functional programming一书中讨论了这个主题,但是有一个free excerpt available,它显示了(恕我直言)你可以在F#中使用的最有趣的模式。假设您要实现矩形绘图(用户按下按钮,移动鼠标并释放按钮)。在F#中,您可以编写如下内容:

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 }

这是一种非常迫切的方法(采用通常的实用F#风格),但它避免使用可变状态来存储当前的绘图状态和存储初始位置。它可以变得更加实用,我写了一个图书馆,作为我的硕士论文的一部分,在接下来的几天里my blog上可以找到它。

功能性反应式编程是一种功能更强大的方法,但我觉得它更难使用,因为它依赖于非常先进的Haskell功能(如箭头)。然而,在很多情况下它非常优雅。它的局限性在于您无法轻松编码状态机(这是反应程序的有用心理模型)。使用上面的F#技术非常容易。

答案 4 :(得分:17)

无论您使用的是混合功能/ OO语言(如F#或OCaml),还是像Haskell这样的纯功能语言,副作用都降级为IO monad,它的主要是的情况管理GUI所需的大量工作更像是一种“副作用”,而不是纯粹的功能算法。

尽管如此,functional GUIs已经有了一些非常可靠的研究。甚至还有一些(大部分)功能性工具包,例如FudgetsFranTk

答案 5 :(得分:15)

你可以看看Don Syme在F#上的系列,他演示了如何创建一个gui。以下链接是该系列的第三部分(您可以从那里链接到另外两个部分)。

使用F#进行WPF开发将是一个非常有趣的GUI范例......

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

答案 6 :(得分:12)

功能反应式编程背后的一个令人心碎的想法是让事件处理函数产生对事件和下一个事件处理函数的两种反应。因此,演进系统被表示为一系列事件处理功能。

对我来说,学习Yampa成为了正确地完成功能生产功能的关键。有一些关于Yampa的好文章。我推荐The Yampa Arcade:

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf(幻灯片,PDF) http://www.cs.nott.ac.uk/~nhn/Publications/hw2003.pdf(完整文章,PDF)

Haskell.org上的Yampa有一个维基页面

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

原Yampa主页:

http://www.haskell.org/yampa(不幸的是,目前已被打破)

答案 7 :(得分:6)

可以找到艾略特关于FRP的演讲here

此外,不是一个真正的答案,而是一个评论和一些想法:某种程度上术语&#34;功能GUI&#34;看起来有点像矛盾(纯粹和IO在同一个词中)。

但我的模糊理解是,功能性GUI编程是关于声明性地定义时间相关函数,该函数采用(实际)时间相关的用户输入并产生时间相关的GUI输出。

换句话说,这个函数被声明地定义为微分方程,而不是强制使用可变状态的算法。

因此在传统的FP中,使用时间无关的函数,而在FRP中,使用时间相关的函数作为描述程序的构建块。

让我们考虑模拟弹簧上的球,用户可以与之交互。球的位置是图形输出(在屏幕上),用户推球是按键(输入)。

在FRP中描述这个模拟程序(根据我的理解)是通过单个微分方程(声明性地)来完成的:加速度*质量= - 弹簧伸展量*弹簧常数+用户施加的力。

以下是ELM上的视频,说明了这一观点。

答案 8 :(得分:6)

自从首次提出这个问题以来,功能反应式编程已经成为Elm的主流。

我建议在http://elm-lang.org查看,其中还有一些非常出色的互动教程,介绍如何制作功能齐全的浏览器内GUI。

它允许您创建功能齐全的GUI,您需要自己提供的代码只包含纯函数。我个人发现它比各种Haskell GUI框架更容易进入。

答案 9 :(得分:5)

截至2016年,还有几个相对成熟的Haskell FRP框架,如Sodium和Reflex(以及Netwire)。

Manning book on Functional Reactive Programming展示了Sodium的Java版本,用于工作示例,并说明了与命令式和基于Actor的方法相比,FRP GUI代码库的行为和扩展方式。

最近还有一篇关于Arrowized FRP的文章,以及在法律规定的纯FRP设置中加入副作用,IO和变异的前景:http://haskell.cs.yale.edu/wp-content/uploads/2015/10/dwc-yale-formatted-dissertation.pdf

另外值得注意的是,像ReactJS和Angular等许多其他JavaScript框架已经或正在朝着使用FRP或其他功能方法来实现可扩展和可组合的GUI组件。

答案 10 :(得分:4)

像XUL这样的标记语言允许您以声明方式构建GUI。

答案 11 :(得分:4)

为了解决这个问题,我发表了一些关于使用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/

我还打算做一个视频教程来完成这个系列,并展示F#如何在UX编程中做出贡献。

我只是在这里谈论F#。

-Fahad

答案 12 :(得分:2)

所有这些其他答案都建立在函数式编程之上,但是做出了很多自己的设计决策。一个基本上完全由函数和简单抽象数据类型构建的库是gloss。以下是来自

play函数的类型
-- | 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 ()

正如您所看到的,它完全通过提供简单抽象类型的纯函数来完成,其他库可以帮助您。

答案 13 :(得分:1)

Haskell新手注意到的最明显的创新是,与外部世界沟通的不纯净世界与计算和算法的纯粹世界之间存在着分离。一个常见的初学者问题是&#34;如何摆脱IO,即将IO a转换为a?&#34;它的方法是使用monad(或其他抽象)来编写执行IO和链效果的代码。该代码从外部世界收集数据,创建它的模型,进行一些计算,可能通过使用纯代码,并输出结果。

就上述模型而言,我没有看到在IO monad中操纵GUI的任何严重错误。这种风格产生的最大问题是模块不再可组合,即我失去了大部分关于程序中语句的全局执行顺序的知识。为了恢复它,我必须在并发的命令式GUI代码中应用类似的推理。同时,对于不纯的非GUI代码,由于IO monad的>==运算符的定义(至少只有一个线程),执行顺序是显而易见的。对于纯代码,它根本不重要,除非在极端情况下提高性能或避免评估导致

控制台和图形IO之间最大的哲学区别是,实现前者的程序通常以同步方式编写。这是可能的,因为(除了信号和其他打开的文件描述符)只有一个事件源:通常称为stdin的字节流。 GUI本质上是异步的,并且必须对键盘事件和鼠标点击作出反应。

以功能方式执行异步IO的流行哲学称为功能反应编程(FRP)。由于ReactiveX等库和Elm等框架,它最近在不纯的非函数语言中获得了很大的吸引力。简而言之,它就像查看GUI元素和其他东西(如文件,时钟,闹钟,键盘,鼠标)作为事件源,称为&#34; observables&#34;,它们发出事件流。这些事件使用熟悉的运算符(例如mapfoldlzipfilterconcatjoin等进行组合,以生成新流。这很有用,因为程序状态本身可以被视为程序的scanl . map reactToEvents $ zipN <eventStreams>,其中N等于程序曾经考虑过的可观察数量。

使用FRP observable可以恢复可组合性,因为流中的事件是按时排序的。原因是事件流抽象使得可以将所有可观察量视为黑盒子。最终,使用运算符组合事件流会在执行时返回一些本地排序。这迫使我对我的程序实际依赖的不变量更加诚实,类似于Haskell中所有函数必须以引用方式透明的方式:如果我想从程序的另一部分提取数据,我必须明确ad为我的函数声明了一个合适的类型。 (IO monad是一种用于编写不纯的代码的特定于域的语言,有效地避免了这种情况)

答案 14 :(得分:-20)

功能编程可能已经从我在大学时开始,但我记得功能编程系统的要点是阻止程序员创建任何“副作用”。然而,由于产生的副作用,用户购买软件,例如,更新用户界面。