0924

{done}GTD190020:【翻译】Functor-Oriented Programming

http://r6.ca/blog/20171010T001746Z.html

 

My style of Haskell programming has been evolving over the 15 years that I have been working with it. It is turning into something that I would like to call “functor oriented programming”. The traditional use of typed functional programming focuses on data types. One defines data types to model the data structures that your program operates on, and one writes functions to transform between these structures. One of the primary goals in this traditional methodology is to create data structures that exclude erroneous states to the extent that is reasonably possible. As long as one ensures that pattern matching is complete, then the type system catches many errors that would otherwise lead to these erroneous states, which have been crafted to be unrepresentable.

Functor oriented programming is a refinement of this traditional focus on data types. I was reminded of this concept recently when I was working with wren’s fantastic unification-fd library. With functor oriented programming, one divides data structures into layers of functors that, when composed together, form the data structures that your program operates on. Instead of writing transformations between data structures, one writes natural transformations between functors, where a natural transformation between functors F and G is a polymorphic function of type forall a. F a -> G a. While traditional functions often operate on products of multiple inputs and/or outputs, with functor oriented programming one will often see functions operating on compositions of functors, including but not limited to distribution functions of type forall a. F (G a) -> G (F a) and half-way distribution functions forall a. F (G a) -> G (H a), and many others.

By dividing data structures up into layers of functors, one can create a separation of concerns that does not occur in traditional functional programming. With functor oriented programming, polymorphism is not necessarily about using functions polymorphically. Instead, polymorphism provides correctness guarantees by ensuring that a particular function can only touch the specific layers of functors in its type signature and is independent of the rest of the data structure. One benefits from polymorphism even when a function is only ever invoked at a single type.

The appearance of many natural transformations is one hallmark of functor oriented programming. Higher-order natural transformations will invoke Rank2Types and RankNTypes, which is another hallmark of functor oriented programming. Other hallmarks of functor oriented programming include open recursive types, which allows one to divide up recursive types into their layers of recursion and create natural transformations that operate on a single layer at a time. Open recursive types plays an important role in wren’s unification library.

As fine of a language that Haskell is, it is not actually that suitable for functor oriented programming. The problem is that, under normal circumstances, there is no reduction or equivalence classes at the type level. For example, the identity functor does not transparently disappear during composition, the Compose functor is not transparently associative, and the Swap functor composed with itself does not reduce to the identity functor. To cope with this one must litter one’s code with newtype wrapper and unwrappers to make all these natural transformations explicit. In principle, these transformations should have no run-time consequences, but when they are used in higher-order ways, unfortunately they sometimes do. Despite the problems, I am not aware of any another practical language that better supports this style of programming. I think Haskell’s higher-kinded type classes and the progression of MonadApplicativeFoldableTraversable, etc. classes have been instrumental in leading to the development of this style of programming as they further motivate the division of one’s data structures into these layers of functors.

I have been thinking about writing this post for a few years now, and wanted to write something convincing; however, I do not think I am up to the task. Instead of trying to persuade the reader, I have elected to try to simply name and describe this style of programming so that the reader might notice it themselves when reading and writing code. Hopefully someone more capable than me can evangelize this approach, and perhaps even create a practical language suitable for this style of programming.

 

我一直在使用Haskell编程的风格已经在15年了。它正在变成我想称之为“面向函数的程序设计”的东西。类型函数式编程的传统使用侧重于数据类型。一个定义数据类型来建模程序运行的数据结构,一个函数用于在这些结构之间转换。这种传统方法的主要目标之一是创建将错误状态排除在合理可能范围内的数据结构。只要确保模式匹配完成,那么类型系统就会捕获许多错误,否则这些错误会导致这些错误的状态,这些错误状态被制作成无法代表的状态。

基于Functor的编程是对传统的数据类型的细化。当我和wren的梦幻般的统一图书馆一起工作时,我最近被提醒了这个概念。使用面向函数的程序设计,将数据结构分为多层函数,当组合在一起时,形成程序运行的数据结构。编写数据结构之间的转换,而不是在函子之间编写自然变换,其中函数F和G之间的自然变换是所有类型的多态函数。 F a - > G a。虽然传统功能通常对多个输入和/或输出的产品进行操作,但是通过面向函数的程序设计,通常会看到对函子的组合进行操作的功能,包括但不限于所有类型的分配函数。 F(G a) - > G(F a)和中途分配函数。 F(G a)→G(H a)等。

通过将数据结构分成多个函子层,可以创建传统功能编程中不会出现的问题分离。使用面向函子的程序设计,多态性不一定是关于多功能函数的使用。相反,多态性通过确保特定功能只能触摸其类型签名中的函子的特定层并且独立于其余的数据结构来提供正确性保证。即使某个功能仅在单一类型中被调用,也可以从多态性中受益。

许多自然变换的出现是面向函子的程序设计的一个标志。高阶自然变换将调用Rank2Types和RankNTypes,这是面向函子的编程的另一个标志。面向函数的编程的其他标志包括开放式递归类型,它允许将递归类型分解为其递归层,并创建一次在单个层上操作的自然变换。打开的递归类型在wren的统一库中起着重要的作用。

和Haskell一样的语言一样好,实际上并不适合用于面向函数的编程。问题是,在正常情况下,类型级别没有减少或等价类。例如,身份函子在组合期间不会透明地消失,Compose函子不是透明的关联,并且由自己构成的Swap函子不会减少到身份函子。为了应对这个问题,必须使用newtype包装器和解包器对所有这些自然变换进行排除。原则上,这些转换应该没有运行时的后果,但是当它们以高阶方式使用时,不幸的是他们有时会这样做。尽管存在问题,但我并不知道任何其他实用的语言能更好的支持这种编程风格。我认为Haskell的高级类型类和Monad,Applicative,Foldable,Traversable等等的进展有助于开发这种风格的编程,因为它们进一步激励了将数据结构分为这些层函子。

我一直在考虑写这篇文章几年,想写一些令人信服的东西;不过,我不认为我有这个任务。而不是试图说服读者,我选择尝试简单地命名和描述这种编程风格,以便读者在阅读和编写代码时可能会注意到自己。希望比我更有能力的人能传福音,甚至可以创造一种适合这种风格的编程的实用语言。

 

Tags

Responses

posted on 2017-10-12 09:43  0924  阅读(199)  评论(0编辑  收藏  举报

导航