可以进行功能GUI编程吗?[关闭]


404

我最近抓到了FP错误(试图学习Haskell),到目前为止我所看到的内容(一流的函数,惰性评估以及所有其他优点)给我留下了深刻的印象。我还不是专家,但是我已经开始发现“功能上”的推理比基本算法的必要性更容易(而且我很难回头去找)。

但是,当前FP似乎保持不变的一个领域是GUI编程。Haskell方法似乎只是包装命令式GUI工具包(例如GTK +或wxWidgets),并使用“ do”块来模拟命令式样式。我没有使用过F#,但是我的理解是,将OOP与.NET类一起使用时,它可以执行类似的操作。显然,这是有充分的理由的-当前的GUI编程都是关于IO和副作用的,因此在大多数当前的框架中不可能进行纯功能编程。

我的问题是,是否可以使用功能性方法进行GUI编程?我很难想象这在实践中会是什么样子。是否有人知道尝试这种方法的任何框架(实验框架或其他框架)(甚至是为功能性语言从头开始设计的任何框架)?还是仅使用混合方法的解决方案,对于GUI部件使用OOP,对于逻辑使用FP?(我只是出于好奇而问-我很想认为FP是“未来”,但是GUI编程似乎需要填补很大的空白。)


7
看过Common Lisp和OCaml中的GUI后,我会说这更可能是引起问题的Haskell的惰性。
new123456 2012年

5
@ new123456 Common Lisp虽然不是一种功能语言,但它可以处理可变数据并具有副作用
Electric Coffee

3
@ElectricCoffee Lisp是一种非常灵活的语言,能够以许多不同的样式使用,并且许多人选择以功能样式使用Lisp。
chrismamo1

7
根据我的经验(尽管我仍在努力相信并学习更多),FRP在GUI编程中确实达到了极限。对于80%的用例来说,它是很好用和优雅的,但是功能丰富的窗口小部件需要对其内部状态(例如搜索组合框等)进行非常精确的控制,并且FRP会妨碍您的使用。当务之急并不总是邪恶的。尝试最小化命令式代码的数量是好的,但是将其删除100%?尚未看到它可用于非琐碎的UI开发。
AlexG

8
@ElectricCoffee“通用Lisp并不是功能语言”。Lisp是所有功能语言的母亲。您的意思是Lisp并不纯洁。
乔恩·哈罗普

Answers:


183

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

这并不是真正的“ Haskell方法”,而是通过命令式界面最直接地绑定到命令式GUI工具包的方式。Haskell恰好具有相当突出的绑定。

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

一些例子是:

对于不熟悉Haskell,Flapjax的那些人,http://www.flapjax-lang.org/ 是在JavaScript之上的功能性反应式编程的实现。


32
请参阅Conal Elliott的有关水果的论文,以深入,详细地描述该技术和决策:conal.net/papers/genuinely-functional-guis.pdf 我已经使用这种风格进行纯功能GUI编程已有几个月了。 。我喜欢它,它从命令式UI程序的意大利面中解脱了过来,这似乎比大多数命令式编程要糟糕。
luqui 2010年

44
我100%同意这一点。明确地说:之所以经常使用现有GUI工具包,是因为它们存在。与它们的接口趋向于命令性和不纯的原因是因为工具箱趋向于命令性和不纯性。工具包趋向于命令性和不纯的原因是因为它们依赖的操作系统趋向于命令性和不纯性。但是,从根本上没有要求其中任何一个都是不纯的:这些工具箱具有功能绑定,功能性工具箱甚至功能性操作系统。
约尔格W¯¯米塔格

16
这只是懒惰的问题。(坏双关语意。)
约尔格W¯¯米塔格

10
总有一天,所有GUI设计都将通过所见即所得(WYSIWYG)来实现,并在功能上实现逻辑。这是我的预测。
BlueRaja-Danny Pflughoeft 2010年

23
luqui提到的纸似乎已经死了。不过,在Conal Elliott的网站上有一个工作链接:conal.net/papers/genuinely-functional-guis.pdf
aganders3 2011年

74

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

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

康纳尔·埃利奥特(Conal Elliott)和其他一些人由于试图找到适合FRP的抽象而在家庭手工业中有所作为。Haskell中有FRP概念的几种实现。

您可以考虑从Conal的最新“推拉功能响应式编程”文章开始,但是还有其他一些(较旧的)实现,其中一些链接来自haskell.org网站。康纳尔(Conal)有覆盖整个领域的诀窍,可以阅读他的论文而无需参考以前的内容。

为了了解如何将这种方法用于GUI开发,您可能需要看看Fudgets,尽管它们在90年代中期设计得越来越长,但确实提供了可靠的FRP方法GUI设计。


我想补充一下“响应式扩展”(FRP库;但是不是FP)的使用率上升的情况,该类最初是为C#编写的,然后移植到Java(RxJava)和JavaScript(RxJS)以及各种语言。到目前为止,Angular 2广泛使用了RxJS。
srph

63

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有时会感到有些奇怪和不清楚,是否使用了功能观点看法)。


12
尽管XAML本质上是声明性的,但MVVM是否真正鼓励了编程的功能风格?视图模型的整个概念(其工作是跟踪视图的状态(并实现一个称为INotifyPropertyChanged万物的接口))对我而言似乎与FP相反。我绝对不是FP方面的专家,也许我过于专注于不变性方面而不是声明性方面,但是我很难理解MVVM模式(通常使用)是FP的一个例子。
devuxer

1
@devuxer我会认为确实如此。我认为没有人会现实地将FP用于严格的不可变代码。相反,您可以决定可变性边界的位置,并在所有其他级别上保持不变-在这种情况下,每个人都可以假定状态是不变的,除了实际上会改变状态的那一小部分。这类似于HTML的工作方式-是的,您拥有不可变的DOM,但是无论何时导航,仍然必须构建一个新的DOM。INotifyPropertyChanged只是您传递给需要处理GUI更新的任何地方的更新功能-这是一个延迟修复程序。
a安2015年

3
史蒂文·彭伯顿(Steven Pemberton)在F#和WPF上写了2篇很棒的文章,在第二篇文章末尾他对F#与WPF 开发的思考增加了这一讨论。另外两个令我着迷的例子是,在事件驱动的MVVM中使用功能控制器,以及在Flying Frog Consultancy 在WPF控件演示中使用区分的联合和递归构造简单的界面。
放克

29

实际上,我会说函数编程(F#)是比C#更好的用户界面编程工具。您只需要稍微不同地考虑问题即可。

我将在第16章的函数式编程书中讨论此主题,但是有一个免费摘录,其中显示了(IMHO)您可以在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#样式中),但是它避免使用可变状态来存储图形的当前状态和存储初始位置。但是,可以使它变得更加功能强大,我在硕士论文中编写了一个库来完成该任务,此库将在未来几天内在我的博客中提供。

功能性反应式编程是一种更具功能性的方法,但是我发现它很难使用,因为它依赖于相当高级的Haskell功能(例如箭头)。但是,在很多情况下它都是非常优雅的。它的局限性在于,您不能轻松地对状态机进行编码(这对于反应式程序是有用的思维模型)。使用上面的F#技术,这非常容易。


7
+1这反映了我们的经验,已使用合并器库和编写了一些F#的生产GUI IObservable
乔恩·哈罗普

自从向.NET库引入反应性扩展以来,对FRP的评论是否发生了变化?
Fsharp Pete

1
以下是有关Arrowized FRP的一些研究,以及如何在不违反法律的情况下在Arrowized FRP中嵌入效果和突变:haskell.cs.yale.edu/wp-content/uploads/2015/10/…尽管大多数FRP库使用Monads甚至是适用性,因此要求使用Arrows是不正确的)。
埃里克·卡普伦

17

无论您是使用混合功能/ OO语言(例如F#或OCaml),还是使用纯函数式语言(例如Haskell),其副作用都归咎于IO monad,通常情况下,管理GUI需要大量工作比起纯粹的功能算法,它更像是“副作用”。

就是说,对功能GUI进行了一些真正扎实的研究。甚至还有一些(大部分)功能性工具箱,例如FudgetsFranTk


6
“功能GUI”链接断开:(已缓存:webcache.googleusercontent.com/search?q=
Dan Burton 2010年


12

功能响应式编程背后的开阔思路之一是具有事件处理功能,该功能既对事件做出反应,又对下一个事件处理函数产生反应。因此,不断发展的系统被表示为一系列事件处理功能。

对我来说,学习Yampa成为正确获取功能产生功能的关键。有一些关于扬帕的好论文。我推荐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的Wiki页面。

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

原始的扬帕主页:

http://www.haskell.org/yampa(不幸的是此刻已损坏)


1
该链接已断开很长时间。试试这个Yampa
CoR 2012年

7

自从首次提出这个问题以来,Elm将函数式反应式编程变得更加主流。

我建议在http://elm-lang.org上进行检查,该书也提供了一些非常出色的交互式教程,介绍如何制作功能齐全的浏览器内GUI。

它允许您制作功能齐全的GUI,其中您需要提供的代码仅由纯函数组成。我个人发现,比起各种Haskell GUI框架要容易得多。


这是Elm背后的FRP原始论文。但自2016年5月以来,Elm不再是FRP语言
icc97

6

可以在这里找到Elliot关于FRP的演讲。

另外,不是真正的答案,而是言论和一些想法:“功能GUI”一词似乎有点像矛盾之词(在同一术语中为“纯”和“ IO”)。

但是我模糊的理解是,功能GUI编程是要声明性地定义一个时间相关的函数,该函数接受(实时)时间相关的用户输入并生成时间相关的GUI输出。

换句话说,此函数的定义类似于微分方程,而不是通过强制使用可变状态的算法来定义。

因此,在常规FP中,一个使用时间独立功能,而在FRP中,一个使用时间相关功能作为描述程序的基础。

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

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

这是有关ELM的视频,阐明了这种观点。


5

截至2016年,Haskell还有其他一些相对成熟的FRP框架,例如Sodium和Reflex(还有Netwire)。

曼宁书官能团反应程序展示了Java版本钠,对工作的例子,说明了FRP GUI代码库的行为和规模相比如何势在必行以及基于演员的方法。

最近还有一篇关于Arrowized FRP的论文,以及将副作用,IO和突变纳入遵守法律的纯FRP设置的前景:http : //haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formatted-dissertation.pdf

还值得注意的是,诸如ReactJS和Angular之类的JavaScript框架以及许多其他框架已经或正在使用FRP或其他功能性方法来实现可扩展且可组合的GUI组件。


根据钠的github自述文件
mac10688,2017年



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

如您所见,它完全通过提供具有简单抽象类型的纯函数来工作,而其他库则可以帮助您。


1

对Haskell陌生的人来说,最明显的创新之处在于,与外界沟通有关的不纯世界与纯粹的计算和算法世界之间存在着分离。初学者经常问的一个问题是“我如何摆脱(IO即转化IO a为)a?” 达到此目的的方法是使用monad(或其他抽象方法)编写执行IO并链接效果的代码。该代码从外界收集数据,创建它的模型,可能通过使用纯代码进行一些计算,然后输出结果。

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

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

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

使用FRP观察值可以恢复可组合性,因为流中的事件是按时间排序的。原因是事件流抽象使将所有可观察对象视为黑盒成为可能。最终,使用运算符组合事件流会在执行时返回一些本地顺序。这迫使我更加诚实地了解程序真正依赖于哪些不变量,类似于Haskell中所有函数必须参照透明的方式:如果我想从程序的另一部分提取数据,则必须明确广告声明适合我的功能的类型。(IO monad是用于编写不纯代码的特定于域的语言,有效地规避了此问题)


-22

函数编程可能从我上大学时就已经发展了,但是正如我回想的那样,函数编程系统的主要目的是阻止程序员产生任何“副作用”。但是,由于产生的副作用(例如更新UI),用户购买软件。


25
我认为您误解了这一点:不是函数式编程对世界没有任何外部影响,而是所有程序完全无用!相反,函数式编程可让您隔离IO,从而知道哪些位在使用它,哪些位在不使用它。
蒂洪·耶维斯

Whoooooooosh x20
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.