函数式编程中的“免费”风格有哪些优缺点?

我知道在某些语言(Haskell?)中,努力的目标是实现无点式的风格,或者永远不要通过名称明确地引用函数参数。 这是一个非常难以理解的概念,但是这可能会帮助我理解这种风格的优点(或者甚至是缺点)。 谁能解释一下?

我相信目的是要简洁,把stream水线的计算expression成function的组成部分,而不是把思路贯穿于整个过程。 简单的例子(在F#中) – 给定:

let sum = List.sum let sqr = List.map (fun x -> x * x) 

用于:

 > sum [3;4;5] 12 > sqr [3;4;5] [9;16;25] 

我们可以将“平方和”函数表示为:

 let sumsqr x = sum (sqr x) 

并使用像:

 > sumsqr [3;4;5] 50 

或者我们可以通过pipe道x来定义它:

 let sumsqr x = x |> sqr |> sum 

用这种方式写的,很明显x只是被传递给一个函数序列的“线程”。 直接构图看起来好多了:

 let sumsqr = sqr >> sum 

这更简洁,它是对我们所做事情的不同思考方式。 构成function而不是想象参数stream经的过程。 我们没有描述sumsqr是如何工作的。 我们正在描述它什么。

PS:一个有趣的方式来让你的脑袋周围组成是尝试编程的拼接语言,如福斯,喜悦,因素等这些可以被认为是除了组成(Forth : sumsqr sqr sum ; ),其中词之间的空格是组合运算符

PPS:也许其他人可以评论性能差异。 在我看来,组成可能会降低GC的压力,通过使编译器更明显的是,不需要像pipe道中那样产生中间值; 帮助使所谓的“砍伐森林”问题更加易于处理。

一些作者认为无点风格是最终的函数式编程风格。 简单地说, t1 -> t2的函数描述了一个从t1types的元素到另一个types为t2元素的转换。 这个想法是,“有意义的”函数(使用显式variables编写)强调元素 (当你写\x -> ... x ... ,你正在描述元素x发生了什么),而“无点”函数(不使用variables表示)强调转换本身,作为简单变换的组合。 倡导无点风格的人认为,转变确实应该是核心概念,有意义的符号虽然容易使用,却把我们从这个崇高的理想中分化出来。

免点function编程已经有很长时间了。 自从1924年摩西·舍恩芬克尔开创性地开展工作以来,逻辑学家已经知道了这一逻辑学家,并且一直是第一次研究罗伯特·费斯(Robert Feys)和哈斯克尔·库里(Haskell Curry)在20世纪50年代将成为MLtypes推理的基础。

从基本的组合函数中构build函数的想法非常有吸引力,并且已经应用​​于各种领域,例如从APL派生的数组操作语言,或者像Haskell的Parsec这样的分析器组合函数库。 John Backus是一个值得关注的免费编程的倡导者。 他在1978年的演讲“可以从冯诺依曼风格中解脱出来吗?”中写道:

lambdaexpression式(及其替代规则)能够定义所有可能的可计算函数的所有可能types和任意数量的参数。 这种自由和权力有其弊端和明显的优势。 这与传统语言中不受限制的控制语句的力量是相似的:自由度不受限制就会产生混乱。 如果人们不断地发明新的组合forms以适应这种场合,就像在lambda演算中可以做到的那样,人们将不会熟悉适用于所有目的的less数组合forms的风格或有用特性。 正如结构化编程避免了许多控制语句以获得具有更简单的结构,更好的属性和统一的方法来理解它们的行为一样,函数式编程避开了lambdaexpression式,replace和多个函数types。 从而实现了具有已知有用属性的熟悉functionforms的程序。 这些程序是如此的结构化,他们的行为通常可以通过机械使用与解决高中代数问题类似的代数技术来理解和certificate。

所以他们在这里 无点编程的主要优点是它们强制结构化的组合方式,使得等式推理自然。 “Squiggol”运动的支持者特别宣传了方程式推理(参见[1] [2]),并确实使用了公平份额的无点组合和计算/重写/推理规则。

  • [1] “Bird-Merteensforms主义的介绍” ,Jeremy Gibbons,1994
  • [2] “用香蕉,镜头,信封和带刺线进行function编程” ,Erik Meijer,Maarten Fokkinga和Ross Paterson,1991年

最后,haskellites之间的无点式编程的普及的一个原因是它与类别理论的关系。 在范畴论中,态射(可以被看作是“对象间的转换”)是研究和计算的基本对象。 尽pipe部分结果允许在特定类别中进行推理,但是构build,检查和操纵箭头的常用方法仍然是无点风格,而其他语法(如string图表)也显示了这种“无点”效果。 在编程中,倡导“编程代数”方法的人与类别使用者之间存在着相当紧密的联系(例如,香蕉纸[2]的作者是硬核分类者)。

您可能对Haskell wiki的Pointfree页面感兴趣。

无点式的缺点是相当明显的:阅读真的很痛苦。 我们仍然喜欢使用variables,尽pipe阴影,阿尔法等价等众多的恐怖,这是一个阅读和思考是很自然的符号。 总体思路是,一个复杂的函数(以透明的引用语言)就像一个复杂的pipe道系统:input是参数,它们进入一些pipe道,被应用到内部函数,复制( \x -> (x,x) )或被遗忘的( \x -> () ,pipe道无处)等等。variables表示法对于所有的机器都是很好的暗示:给input名称,输出名称(或者辅助计算)但是你不必描述所有的pipe道计划,小pipe道将不会成为大的pipe道的障碍等等。pipe道内的pipe道数量短于\(f,x,y) -> ((x,y), fxy)是惊人的。 您可以单独跟踪每个variables,也可以读取每个中间pipe道节点,但不必一起看整个机器。 当你使用一种无​​点式的风格时,这是完全明确的,你必须把所有东西都写下来,然后再看,有时候这简直太丑了。

PS:这种pipe道远景与堆栈编程语言密切相关,堆栈编程语言可能是最less量的编程语言(几乎没有使用)。 我会build议尝试在他们的编程只是为了得到它的感觉(因为我会推荐逻辑编程)。 看到因素 , 猫或古老的福斯 。

虽然我被这个无意义的概念所吸引,并用于某些事情,并且同意以前所说的所有的积极态度,但是我发现这些事情都是负面的(有些在上面详述):

  1. 较短的符号减less冗余; 在一个结构严密的组合(ramda.js风格或Haskell中的无点或任何连接语言)中,代码读取比通过一系列const绑定进行线性扫描更复杂,并使用符号荧光笔来查看哪个绑定进入了什么其他下游计算。 除了树与线性结构之外,描述性符号名称的丢失使得该函数难以直观地被理解。 当然,树结构和命名绑定的损失也有很多好处,例如,函数会感觉更普遍 – 不会通过所选的符号名称绑定到某个应用程序域,并且树结构在语义上也是存在的如果绑定被布置,并且可以被顺序理解(lisp let / let * style)。

  2. 只需pipe道或组成一系列function,无点是最简单的,因为这也导致我们人类易于遵循的线性结构。 但是,通过多个接收者进行某些中间计算是非常繁琐的。 有各种各样的元组包装,透镜和其他艰苦的机制进入只是使一些计算可访问,否则将是一些价值绑定的多种用途。 当然,重复的部分可以作为一个单独的函数提取出来,也许这是一个好主意,但也有一些非短函数的参数,即使提取了它的参数也不得不以某种方式通过这两个应用程序进行线程化,然后可能需要记忆function而不实际重复计算。 一个会使用很多convergelensmemoizeuseWidth

  3. 特定于JavaScript:难以随意debugging。 使用let绑定的线性stream,可以很容易地在任何地方添加断点。 使用无点的风格,即使添加了某个断点,价值stream也很难被读取,例如。 你不能只是查询或hover在开发控制台的一些variables。 另外,由于JS中不是原生的,所以ramda.js或类似的库函数会使得堆栈变得相当模糊,特别是在专门的curry中。

  4. 编码脆性,特别是在非平凡尺寸系统和生产中。 如果有新的要求出现,那么上述缺点就会发挥作用(例如,更难以读取下一个维护人员的代码,这个维护人员可能在几个星期之后,也很难跟踪数据stream以进行检查)。 但最重要的是,即使是一些看起来很小而且无辜的新需求,也需要完全不同的代码结构。 有人可能会认为这是一件好事,因为它会清楚地表示新事物,但是重写大量的无点代码非常耗时,所以我们没有提到testing。 因此,感觉到较松散,较less结构化,基于词汇分配的编码可以更快地重新使用。 特别是如果编码是探索性的,并且在具有奇怪惯例(时间等)的人类数据领域中很less能够100%准确地捕捉到,并且可能总是有即将到来的请求来更精确地处理或者更多地处理客户,无论哪种方法导致更快的转轴重要性。