我最近发现了FP bug(试图学习Haskell),到目前为止我已经看到了(一流的功能,懒惰的评估和所有其他好东西)。 我还不是专家,但是我已经开始发现在功能上比在基本算法上强制推理更容易(而且我很难回到我必须去的地方)。
然而,当前FP看起来平坦的一个领域是GUI编程。 Haskell方法似乎只是包装命令式GUI工具包(例如GTK +或wxWidgets)并使用“do”块来模拟命令式样式。 我没有使用过F#,但我的理解是它使用OOP和.NET类做了类似的事情。 显然,有一个很好的理由 - 当前的GUI编程完全是关于IO和副作用的,所以对于大多数当前的框架来说,纯函数式编程是不可能的。
我的问题是,是否可以使用GUI编程的功能方法? 我无法想象在实践中这会是什么样子。 有没有人知道尝试过这种事情的任何框架,无论是实验的还是其他的(或者甚至是为功能语言设计的任何框架)? 或者只是使用混合方法的解决方案,其中OOP用于GUI部件,FP用于逻辑? (我只是想出于好奇心 - 我很想认为FP是“未来”,但GUI编程似乎是一个非常大的漏洞。)
#1楼
Elliot关于FRP的演讲可以在这里找到。
另外,不是一个答案,而是一个评论和一些想法 :不知何故,术语“功能GUI”似乎有点像矛盾(纯粹和IO在同一个词中)。
但我的模糊理解是,功能性GUI编程是关于声明性地定义时间相关函数,该函数采用(实际)时间相关的用户输入并产生时间相关的GUI输出。
换句话说,这个函数被声明地定义为微分方程,而不是强制使用可变状态的算法。
因此,在传统的FP中,使用时间无关的功能,而在FRP中,使用时间相关的功能作为用于描述程序的构建块。
让我们考虑模拟用户可以与之交互的弹簧上的球。 球的位置是图形输出(在屏幕上),用户推球是按键(输入)。
在FRP中描述这个模拟程序(根据我的理解)是通过单个微分方程(声明性地)来完成的:加速度*质量= - 弹簧伸展量*弹簧常数+用户施加的力。
这是一段关于ELM的视频,展示了这一观点。
#2楼
无论您使用的是混合功能/ OO语言(如F#或OCaml),还是使用纯函数语言(如Haskell),副作用都降级为IO monad, 大多数情况下管理GUI需要大量工作更像是一种“副作用”而不是纯粹的功能算法。
也就是说, 功能GUI已经有了一些非常可靠的研究。 甚至还有一些(大部分)功能性工具包,如Fudgets或FranTk 。
#3楼
Haskell方法似乎只是包装命令式GUI工具包(例如GTK +或wxWidgets)并使用“do”块来模拟命令式样式
这不是真正的“Haskell方法” - 这就是你最直接地通过命令式界面绑定到命令式GUI工具包的方式。 Haskell碰巧有相当突出的绑定。
有几种中等成熟或更实验的纯功能/声明式GUI方法,主要是在Haskell中,主要使用功能性反应式编程。
一些例子是:
- reflex-platform, https://github.com/reflex-frp/reflex-platform
- 葡萄柚, http://hackage.haskell.org/package/grapefruit-ui-gtk
- 反应, http://hackage.haskell.org/package/reactive-glut
- wxFruit, http: //hackage.haskell.org/package/wxFruit
- 反应性香蕉, http://hackage.haskell.org/package/reactive-banana
对于那些不熟悉Haskell的人,Flapjax, http ://www.flapjax-lang.org/是基于JavaScript的功能性反应式编程的实现。
#4楼
你可以看看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/
#5楼
我的问题是,是否可以使用GUI编程的功能方法?
您正在寻找的关键词是“功能反应式编程”(FRP)。
Conal Elliott和其他一些人试图找到适合FRP的抽象方法,从而成为一个小屋行业。 Haskell中有几种FRP概念的实现。
您可以考虑从Conal最新的“Push-Pull功能反应式编程”论文开始,但还有其他一些(较旧的)实现,其中一些来自haskell.org网站 。 Conal有覆盖整个领域的诀窍,他的论文可以在不参考之前的内容的情况下阅读。
为了了解这种方法如何用于GUI开发,你可能想看一下Fudgets ,虽然它现在有点长,在90年代中期设计,确实提供了坚实的FRP方法到GUI设计。
#6楼
我实际上会说功能编程(F#)是比C#更好的用户界面编程工具。 你只需要稍微考虑一下这个问题。
我在第16章的函数式编程书中讨论了这个主题,但是有一个免费的摘录 ,它显示了(恕我直言)你可以在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楼
像XUL这样的标记语言允许您以声明方式构建GUI。
#8楼
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有时感觉有点奇怪而且不清楚你是否使用了这个功能观点看法)。
#9楼
所有这些其他答案都建立在函数式编程之上,但是他们做出了很多自己的设计决策。 一个基本上完全由函数和简单的抽象数据类型构建的库是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 ()
正如您所看到的,它完全通过提供简单抽象类型的纯函数来完成,其他库可以帮助您。
#10楼
截至2016年,还有几个相对成熟的Haskell FRP框架,如Sodium和Reflex(以及Netwire)。
关于功能反应式编程的Manning书籍展示了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组件。
#11楼
自从第一次提出这个问题以来,功能性反应式编程已经成为Elm的主流。
我建议在http://elm-lang.org上查看它,它还有一些真正优秀的交互式教程,介绍如何制作一个功能齐全的浏览器内GUI。
它允许您创建功能完备的GUI,您需要自己提供的代码只包含纯函数。 我个人发现它比各种Haskell GUI框架更容易进入。
#12楼
Haskell新手注意到的最明显的创新是,与外部世界沟通的不纯净世界与计算和算法的纯粹世界之间存在着分离。 一个常见的新手问题是“我怎样才能摆脱IO
,即转换IO a
成a
?” 它的方法是使用monad(或其他抽象)来编写执行IO和链效果的代码。 该代码从外部世界收集数据,创建它的模型,进行一些计算,可能通过使用纯代码,并输出结果。
就上述模型而言,我没有看到在IO
monad中操作GUI的任何错误。 这种风格产生的最大问题是模块不再可组合,即我失去了大部分关于程序中语句的全局执行顺序的知识。 为了恢复它,我必须在并发的命令式GUI代码中应用类似的推理。 同时,对于不纯的非GUI代码,由于IO
monad的>==
运算符的定义(至少只有一个线程),执行顺序是显而易见的。 对于纯代码,它根本不重要,除了在极端情况下提高性能或避免评估导致⊥
。
控制台和图形IO之间最大的哲学区别是实现前者的程序通常以同步方式编写。 这是可能的,因为(除了信号和其他打开的文件描述符)只有一个事件源:通常称为stdin
的字节流。 GUI虽然本质上是异步的,但必须对键盘事件和鼠标点击做出反应。
以功能方式执行异步IO的流行哲学称为功能反应编程(FRP)。 由于ReactiveX等库和Elm等框架,它最近在不纯的非功能语言中获得了很大的吸引力。 简而言之,它就像查看GUI元素和其他东西(如文件,时钟,闹钟,键盘,鼠标)作为事件源,称为“observables”,发出事件流。 这些事件使用熟悉的运算符(例如map
, foldl
, zip
, filter
, concat
, join
等)进行组合,以生成新流。 这很有用,因为程序状态本身可以看作是scanl . map reactToEvents $ zipN <eventStreams>
scanl . map reactToEvents $ zipN <eventStreams>
,其中N
等于程序考虑的可观察数量。
使用FRP observable可以恢复可组合性,因为流中的事件是按时排序的。 原因是事件流抽象使得可以将所有可观察量视为黑盒子。 最终,使用运算符组合事件流会在执行时返回一些本地排序。 这迫使我对我的程序实际依赖的不变量更加诚实,类似于Haskell中所有函数必须以引用方式透明的方式:如果我想从程序的另一部分提取数据,我必须明确ad为我的函数声明了一个合适的类型。 (IO monad是一种用于编写不纯的代码的特定于域的语言,有效地避免了这种情况)
#13楼
功能反应式编程背后的一个令人心碎的想法是让事件处理功能产生对事件和下一个事件处理功能的两种反应。 因此,演进系统被表示为一系列事件处理功能。
对我来说,学习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 (遗憾的是此刻已被打破)
#14楼
为了解决这个问题,我发表了一些使用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
#15楼
功能编程可能已经从我上大学时开始,但我记得功能编程系统的要点是阻止程序员创建任何“副作用”。 然而,由于创建的副作用,例如更新UI,用户购买软件。