功能性 GUI 编程是不是可行? [关闭]
Posted
技术标签:
【中文标题】功能性 GUI 编程是不是可行? [关闭]【英文标题】:Is functional GUI programming possible? [closed]功能性 GUI 编程是否可行? [关闭] 【发布时间】:2011-02-09 23:54:02 【问题描述】:我最近发现了 FP 错误(尝试学习 Haskell),我对迄今为止所看到的(一流的函数、惰性求值和所有其他好东西)印象深刻。我还不是专家,但我已经开始发现“功能性”推理比基本算法的命令性推理更容易(而且我很难回到我必须做的地方)。
然而,当前 FP 似乎失败的一个领域是 GUI 编程。 Haskell 方法似乎只是包装命令式 GUI 工具包(例如 GTK+ 或 wxWidgets)并使用“do”块来模拟命令式样式。我没有使用过 F#,但我的理解是它使用带有 .NET 类的 OOP 做了类似的事情。显然,这样做是有充分理由的——当前的 GUI 编程都是关于 IO 和副作用的,因此对于大多数当前框架来说,纯粹的函数式编程是不可能的。
我的问题是,是否有可能为 GUI 编程提供功能性方法?我很难想象这在实践中会是什么样子。有没有人知道任何尝试这种事情的框架,实验性的或其他的(或者甚至是从头开始为功能语言设计的任何框架)?还是仅使用混合方法的解决方案,GUI 部分使用 OOP,逻辑使用 FP? (我只是出于好奇而问——我很想认为 FP 是“未来”,但 GUI 编程似乎是一个很大的漏洞。)
【问题讨论】:
查看过 Common Lisp 和 OCaml 中的 GUI 后,我想说,更可能的是导致问题的 Haskell 的懒惰。 @new123456 虽然 Common Lisp 不是一种函数式语言,但它可以处理可变数据并包含副作用 @ElectricCoffee Lisp 是一种极其灵活的语言,能够以多种不同的风格使用,许多人选择以函数式风格使用 Lisp。 根据我的经验(虽然我仍然在努力相信它并了解更多)FRP 在 GUI 编程方面确实达到了它的极限;对于 80% 的用例来说,它既漂亮又优雅,但丰富的小部件需要非常精确地控制它们的内部状态(例如搜索组合框等),而 FRP 只是妨碍了。势在必行并不总是邪恶的。尽量减少命令式代码的数量是好的,但要删除 100% 的代码?还没有看到它适用于非平凡的 UI 开发。 @ElectricCoffee “不过,Common Lisp 不是一种函数式语言”。 Lisp 是所有函数式语言之母。你的意思是 Lisp 不纯。 【参考方案1】:Haskell 方法似乎只是包装命令式 GUI 工具包(例如 GTK+ 或 wxWidgets)并使用“do”块来模拟命令式样式
这并不是真正的“Haskell 方法”——这只是通过命令式接口最直接地绑定到命令式 GUI 工具包的方式。 Haskell 恰好有相当突出的绑定。
有几种较为成熟的或更多实验性的纯函数式/声明式 GUI 方法,主要在 Haskell 中,主要使用函数式反应式编程。
一些例子是:
反射平台,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 的函数式反应式编程的实现。
【讨论】:
请参阅 Conal Elliott 的有关水果的论文,以深入了解该技术和决策:conal.net/papers/genuinely-functional-guis.pdf 我几个月来一直在使用这种风格进行纯函数式 GUI 编程。我喜欢它,从命令式 UI 编程的意大利面地狱中解脱出来真是令人愉快,在这方面它似乎比大多数命令式编程更糟糕。 我 100% 同意这一点。说得一清二楚:经常使用现有的 GUI 工具包的原因是因为它们存在。与它们的接口往往是命令式的和不纯的的原因是因为工具包往往是命令式的和不纯的。工具包趋向于命令式和不纯的原因是因为它们所依赖的操作系统往往是命令式和不纯的。但是,从根本上说,没有什么要求其中任何一个是不纯的:这些工具包有功能绑定,有功能性工具包,甚至还有功能性操作系统。 这只是懒惰的问题。 (双关语不好。) 有一天所有的GUI设计都将通过所见即所得的方式实现,逻辑实现功能化。这是我的预测。 luqui 提到的论文似乎已经死了。不过,Conal Elliott 的网站上有一个工作链接:conal.net/papers/genuinely-functional-guis.pdf【参考方案2】:我的问题是,是否有可能为 GUI 编程提供一种功能性方法?
您正在寻找的关键词是“函数式反应式编程”(FRP)。
Conal Elliott 和其他一些人试图为 FRP 找到合适的抽象概念,这有点像家庭手工业。 Haskell 中有几种 FRP 概念的实现。
您可以考虑从 Conal 最新的 "Push-Pull Functional Reactive Programming" 论文开始,但还有其他几个(较旧的)实现,其中一些链接自 haskell.org site。 Conal 擅长涵盖整个领域,他的论文无需参考之前的内容即可阅读。
要了解如何将这种方法用于 GUI 开发,您可能需要查看 Fudgets,虽然它现在有点长,但在 90 年代中期设计,确实如此为 GUI 设计提供可靠的 FRP 方法。
【讨论】:
我想补充一下“反应式扩展”(FRP 库;但是,不是 FP)的兴起,它最初是为 C# 编写的,然后移植到 Java (RxJava) 和 JavaScript (RxJS ) 和各种语言。查看 reactivex.io,Angular 2 大量使用了 RxJS。【参考方案3】: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,这有点不幸——如果你是使用功能的观点)。
【讨论】:
虽然 XAML 本质上是非常声明性的,但 MVVM 真的鼓励函数式编程吗?视图模型的整个概念,其工作是跟踪视图的状态(并实现一个名为INotifyPropertyChanged
的接口),在我看来与 FP 是对立的。我绝对不是 FP 方面的专家,也许我过于关注不变性方面而不是声明性方面,但我很难看到 MVVM 模式(通常使用)是 FP 的一个例子。
@devuxer 我认为确实如此。我认为没有人会真正将 FP 用于严格的不可变代码。相反,你决定你的可变性边界在哪里,并在所有其他级别上工作不可变 - 在这种情况下,每个人都可以假设状态是不可变的,除了实际改变状态的那个微小部分。它类似于 html 的工作方式 - 是的,您拥有不可变的 DOM,但无论何时导航,您仍然需要构建一个新的 DOM。 INotifyPropertyChanged
只是一个更新函数,你可以传递到任何你需要处理 GUI 更新的地方——它是一个延迟修复。
Steven Pemberton 写了 2 篇关于 F# 和 WPF 的精彩帖子,他的 Thoughts on WPF development with F# 在the second post 的末尾加入了这个讨论。另外两个让我感兴趣的例子是在 event driven MVVM 中使用功能控制器,以及在 Flying Frog Consultancy 在 WPF controls demo 中使用可区分联合和递归构造简单接口。【参考方案4】:
我实际上会说函数式编程 (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# 技术。
【讨论】:
+1 这反映了我们的经验,我们使用组合库和IObservable
在 F# 中编写了几个生产 GUI。
自从 .NET 库引入响应式扩展后,对 FRP 的评论是否发生了变化?
这里有一些关于箭头化 FRP 的研究,以及如何在不违反法律的情况下将效果和突变嵌入到箭头化 FRP 中:haskell.cs.yale.edu/wp-content/uploads/2015/10/…(顺便说一句,大多数 FRP 库都使用 Monads 甚至 Applicatives,所以 Arrows 是不正确的是必需的)。【参考方案5】:
无论您是使用 F# 或 OCaml 等混合函数式/OO 语言,还是使用像 Haskell 这样的副作用被归入 IO monad 的纯函数式语言,大多数情况下管理 GUI 所需的大量工作更像是一种“副作用”,而不是纯粹的函数式算法。
也就是说,已经对functional GUIs 进行了一些非常扎实的研究。甚至还有一些(大部分)功能性工具包,例如 Fudgets 或 FranTk。
【讨论】:
“功能性 GUI”链接断开 :( 缓存:webcache.googleusercontent.com/search?q=cache:http://…【参考方案6】:您可以查看 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/
【讨论】:
【参考方案7】:函数响应式编程背后的一个令人大开眼界的想法是让事件处理函数产生对事件的反应和下一个事件处理函数。因此,不断发展的系统被表示为一系列事件处理功能。
对我来说,学习 Yampa 成为正确获得功能产生功能的关键点。有一些关于 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
原Yampa主页:
http://www.haskell.org/yampa(可惜现在坏掉了)
【讨论】:
那个链接坏了很久。试试这个Yampa【参考方案8】:自从第一次提出这个问题以来,Elm 使函数式反应式编程变得更加主流。
我建议在http://elm-lang.org 上查看它,它还有一些关于如何制作功能齐全的浏览器内 GUI 的真正优秀的交互式教程。
它允许您制作功能齐全的 GUI,您需要自己提供的代码仅由纯函数组成。我个人发现它比各种 Haskell GUI 框架更容易上手。
【讨论】:
这是original FRP thesis behind Elm。但也是自 2016 年 5 月以来Elm isn't a FRP language anymore.【参考方案9】:Elliot 关于 FRP 的演讲可以在 here 找到。
此外,不是真正的答案,而是一个评论和一些想法:不知何故,“功能性 GUI”这个词似乎有点像一个矛盾的说法(纯度和 IO 在同一个术语中)。
但我的模糊理解是,函数式 GUI 编程是关于以声明方式定义一个时间相关函数,该函数接受(实时)相关用户输入并产生时间相关 GUI 输出。
换句话说,这个函数被定义为一个微分方程,而不是通过一个算法强制使用可变状态。
所以在传统的 FP 中使用时间无关函数,而在 FRP 中使用时间相关函数作为描述程序的构建块。
让我们考虑在弹簧上模拟一个球,用户可以与之交互。球的位置是图形输出(在屏幕上),用户推球是按键(输入)。
在 FRP 中描述这个模拟程序(根据我的理解)是通过一个微分方程(声明式)完成的:加速度 * 质量 = - 弹簧的拉伸 * 弹簧常数 + 用户施加的力。
这是ELM 上的一个视频,说明了这一观点。
【讨论】:
【参考方案10】:截至 2016 年,还有几个相对成熟的 Haskell FRP 框架,例如 Sodium 和 Reflex(还有 Netwire)。
Manning book on Functional Reactive Programming 展示了 Java 版本的 Sodium,用于工作示例,并说明了与命令式和基于 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 组件。
【讨论】:
根据 Sodium 的 github 自述文件,Sodium 已被弃用,取而代之的是反应性香蕉【参考方案11】:XUL 等标记语言允许您以声明方式构建 GUI。
【讨论】:
【参考方案12】:为了解决这个问题,我发布了我在使用 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# 的上下文中讨论。
-法哈德
【讨论】:
【参考方案13】:所有这些其他答案都是建立在函数式编程之上的,但是他们自己做出了很多设计决策。一个基本上完全由函数和简单抽象数据类型构建的库是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 ()
如您所见,它完全通过提供具有简单抽象类型的纯函数来工作,其他库可以帮助您。
【讨论】:
【参考方案14】:Haskell 新手注意到的最明显的创新是,与外部世界通信有关的不纯世界与计算和算法的纯世界之间存在分离。一个常见的初学者问题是“我怎样才能摆脱IO
,即将IO a
转换为a
?”实现它的方法是使用 monads(或其他抽象)来编写执行 IO 和链效果的代码。此代码从外部世界收集数据,创建数据模型,进行一些计算(可能使用纯代码),然后输出结果。
就上述模型而言,我认为在 IO
monad 中操作 GUI 没有什么大问题。这种风格产生的最大问题是模块不再是可组合的,也就是说,我失去了关于程序中语句的全局执行顺序的大部分知识。为了恢复它,我必须应用与并发命令式 GUI 代码类似的推理。同时,对于不纯的、非 GUI 代码,由于 IO
monad 的 >==
运算符的定义(至少只要只有一个线程),执行顺序是显而易见的。对于纯代码,这根本不重要,除非在极端情况下提高性能或避免评估导致⊥
。
控制台和图形 IO 之间最大的哲学区别是实现前者的程序通常以同步方式编写。这是可能的,因为(撇开信号和其他打开的文件描述符)只有一个事件源:通常称为stdin
的字节流。 GUI 本质上是异步的,并且必须对键盘事件和鼠标点击做出反应。
以函数式方式执行异步 IO 的流行哲学称为函数式响应式编程 (FRP)。由于 ReactiveX 等库和 Elm 等框架,它最近在不纯的、非函数式语言中获得了很大的吸引力。简而言之,这就像查看 GUI 元素和其他事物(例如文件、时钟、闹钟、键盘、鼠标)作为事件源(称为“可观察对象”),它们会发出事件流。这些事件使用熟悉的运算符组合,例如map
、foldl
、zip
、filter
、concat
、join
等,以产生新的流。这很有用,因为程序状态本身可以被视为程序的scanl . map reactToEvents $ zipN <eventStreams>
,其中N
等于程序曾经考虑过的可观察对象的数量。
使用 FRP 可观察对象可以恢复可组合性,因为流中的事件是按时间排序的。原因是事件流抽象使得将所有可观察对象视为黑盒子成为可能。最终,使用运算符组合事件流会在执行时返回一些本地顺序。这迫使我对我的程序实际依赖的不变量更加诚实,类似于 Haskell 中的所有函数都必须是引用透明的:如果我想从程序的另一部分提取数据,我必须明确广告为我的函数声明一个合适的类型。 (IO monad,作为一种用于编写不纯代码的领域特定语言,有效地规避了这一点)
【讨论】:
【参考方案15】:函数式编程可能从我上大学时就开始了,但我记得函数式编程系统的主要目的是阻止程序员产生任何“副作用”。然而,用户购买软件是因为产生的副作用,例如。更新用户界面。
【讨论】:
我认为您误解了重点:并不是说函数式编程对世界没有外部影响——这会使所有程序完全无用!相反,函数式编程允许您隔离 IO,以便您知道哪些位使用它,哪些位不使用。以上是关于功能性 GUI 编程是不是可行? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
寻找一本关于 Windows C++ GUI 编程的书 [关闭]
用于 Windows 的 Java 中的 GUI 编程? [关闭]