我最近发现了FP错误(试图学习Haskell),到目前为止,我对我所看到的(一流函数、惰性求值和所有其他好东西)印象深刻。我还不是专家,但我已经开始发现对基本算法进行“功能性”推理比命令式推理更容易(而且我很难回到我必须回到的地方)。

The one area where current FP seems to fall flat, however, is GUI programming. The Haskell approach seems to be to just wrap imperative GUI toolkits (such as GTK+ or wxWidgets) and to use "do" blocks to simulate an imperative style. I haven't used F#, but my understanding is that it does something similar using OOP with .NET classes. Obviously, there's a good reason for this--current GUI programming is all about IO and side effects, so purely functional programming isn't possible with most current frameworks.

My question is, is it possible to have a functional approach to GUI programming? I'm having trouble imagining what this would look like in practice. Does anyone know of any frameworks, experimental or otherwise, that try this sort of thing (or even any frameworks that are designed from the ground up for a functional language)? Or is the solution to just use a hybrid approach, with OOP for the GUI parts and FP for the logic? (I'm just asking out of curiosity--I'd love to think that FP is "the future," but GUI programming seems like a pretty large hole to fill.)


当前回答

自从第一次提出这个问题以来,函数式响应式编程已经被Elm变得更加主流。

我建议你去http://elm-lang.org上看看,那里也有一些非常优秀的交互式教程,教你如何制作一个功能齐全的浏览器内GUI。

它允许您制作功能齐全的GUI,其中您需要自己提供的代码仅由纯函数组成。我个人认为它比各种Haskell GUI框架更容易上手。

其他回答

我的问题是,是否可能有一种函数式的GUI编程方法?

您正在寻找的关键词是“函数式响应式编程”(FRP)。

Conal Elliott和其他一些人试图为FRP找到正确的抽象,这有点像家庭手工业。在Haskell中有几个FRP概念的实现。

您可能会考虑从Conal最近的“Push-Pull函数式响应式编程”论文开始,但是还有其他一些(更老的)实现,其中一些链接来自haskell.org网站。Conal有一个覆盖整个领域的诀窍,他的论文可以在不参考以前的情况下阅读。

为了感受如何将这种方法用于GUI开发,您可能想要看看Fudgets,虽然它在90年代中期设计,但确实为GUI设计提供了可靠的FRP方法。

自从第一次提出这个问题以来,函数式响应式编程已经被Elm变得更加主流。

我建议你去http://elm-lang.org上看看,那里也有一些非常优秀的交互式教程,教你如何制作一个功能齐全的浏览器内GUI。

它允许您制作功能齐全的GUI,其中您需要自己提供的代码仅由纯函数组成。我个人认为它比各种Haskell GUI框架更容易上手。

所有这些其他答案都建立在函数式编程的基础上,但它们自己做出了很多设计决策。一个基本上完全由函数和简单抽象数据类型构建的库是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 ()

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

你可以看看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/

The most apparent innovation noticed by people new to Haskell is that there is a separation between the impure world that is concerned with communicating with the outside world, and the pure world of computation and algorithms. A frequent beginner question is "How can I get rid of IO, i.e., convert IO a into a?" The way to to it is to use monads (or other abstractions) to write code that performs IO and chains effects. This code gathers data from the outside world, creates a model of it, does some computation, possibly by employing pure code, and outputs the result.

As far as the above model is concerned, I don't see anything terribly wrong with manipulating GUIs in the IO monad. The largest problem that arises from this style is that modules are not composable anymore, i.e., I lose most of my knowledge about the global execution order of statements in my program. To recover it, I have to apply similar reasoning as in concurrent, imperative GUI code. Meanwhile, for impure, non-GUI code the execution order is obvious because of the definition of the IO monad's >== operator (at least as long as there is only one thread). For pure code, it doesn't matter at all, except in corner cases to increase performance or to avoid evaluations resulting in ⊥.

控制台IO和图形化IO之间最大的哲学区别在于,实现前者的程序通常是用同步风格编写的。这是可能的,因为(撇开信号和其他打开的文件描述符不谈)只有一个事件源:通常称为stdin的字节流。gui本质上是异步的,必须对键盘事件和鼠标点击做出反应。

A popular philosophy of doing asynchronous IO in a functional way is called Functional Reactive Programming (FRP). It got a lot of traction recently in impure, non-functional languages thanks to libraries such as ReactiveX, and frameworks such as Elm. In a nutshell, it's like viewing GUI elements and other things (such as files, clocks, alarms, keyboard, mouse) as event sources, called "observables", that emit streams of events. These events are combined using familiar operators such as map, foldl, zip, filter, concat, join, etc., to produce new streams. This is useful because the program state itself can be seen as scanl . map reactToEvents $ zipN <eventStreams> of the program, where N is equal to the number of observables ever considered by the program.

Working with FRP observables makes it possible to recover composability because events in a stream are ordered in time. The reason is that the event stream abstraction makes it possible to view all observables as black boxes. Ultimately, combining event streams using operators gives back some local ordering on execution. This forces me to be much more honest about which invariants my program actually relies on, similar to the way that all functions in Haskell have to be referentially transparent: if I want to pull data from another part of my program, I have to be explicit ad declare an appropriate type for my functions. (The IO monad, being a Domain-Specific language for writing impure code, effectively circumvents this)