Sur*_*rya 61 programming-languages functional-programming
当函数式程序员说某个东西可以组合或不可组合时,他们的意思是什么?
我读过的一些这类陈述是:
j_r*_*ker 56
马塞洛·坎托斯给出了一个很好的解释,但我认为它可以稍微精确一些.
当一些事物可以以某种方式组合以产生相同类型的事物时,一种事物是可组合的.
控制结构可组合性. 像C这样的语言区分表达式,表达式可以使用运算符来生成新的表达式,语句可以使用控制结构组成if,for而"序列控制结构"只是按顺序执行语句.关于这种安排的事情是这两个类别不是平等的 - 许多控制结构使用表达式(例如,通过if选择要执行的分支来评估表达式),但表达式不能使用控制结构(例如,不能返回一个for循环).虽然想要"回归"似乎是疯狂或毫无意义的for循环",实际上将控制结构视为可以存储和传递的第一类对象的一般思想不仅可行而且有用.在像Haskell这样的惰性函数语言中,控制结构如if和for可以表示为普通函数,可以像任何其他术语一样在表达式中操作,启用诸如根据传递的参数"构建"其他函数的函数,并将它们返回给调用者.因此,当C(例如)划分"程序员可能想要"分成两个类别并限制这些类别中的对象组合的方式,Haskell(例如)只有一个类别并且没有强加这样的限制,因此从这个意义上说它提供了更多的可组合性.
线程可组合性. 我假设正如Marcelo Cantos所做的那样,你真的在谈论使用锁/互斥锁的线程的可组合性.这是一个稍微棘手的情况,因为从表面上看,我们可以使用多个锁的线程; 但重要的一点是,我们不能拥有使用多个锁的线程,并保证它们具有的保证.
我们可以将锁定义为一种具有某些可以在其上执行的操作的东西,它具有某些保证.一个保证是:假设有一个锁对象x,然后提供每个调用lock(x)最终调用的进程unlock(x),任何调用lock(x)最终将成功返回x当前线程/进程锁定.这种保证极大地简化了程序行为的推理.
不幸的是,如果世界上有多个锁,那就不再是真的了.如果线程A调用lock(x); lock(y);和线程B调用lock(y); lock(x);那么A抓取锁定x并且B抓取锁定y它们将无限期地等待另一个线程释放另一个锁:死锁.因此,锁是不可组合的,因为当您使用多个锁时,您不能简单地声称该重要保证仍然存在 - 不是没有详细分析代码以查看它如何管理锁.换句话说,您再也不能将功能视为"黑匣子".
可组合的东西是好的,因为它们能够实现抽象,这意味着它们使我们能够在不必关心所有细节的情况下推理代码,并且减少了程序员的认知负担.
j-g*_*tus 32
可组合性的一个简单示例是Linux命令行,其中管道字符允许您以几乎无限多种方式组合简单命令(ls,grep,cat,more等),从而"组合"来自a的大量复杂行为.少数更简单的原语.
可组合性有几个好处:
more),您可以获得一定程度的分页一致性,如果每个命令都要实现自己的机制(和命令),这将是不可能的行标志)进行分页.正如Linux命令行示例所示,可组合性不一定仅限于函数式编程,但概念是相同的:具有执行受限任务的小块代码,并通过适当地路由输出和输入来构建更复杂的功能.
关键是函数式编程非常适合这种情况:使用不可变变量和副作用限制,您可以更容易地编写,因为您不必担心被调用函数在幕后发生的事情 - 比如更新共享变量以便结果对于某些操作序列或访问共享锁将无效,因此某些调用序列将导致死锁.
这是函数式编程可组合性 - 任何函数都只依赖于它的输入参数,输出可以传递给任何可以处理返回值类型的函数.
通过扩展,具有更少的数据类型提供了更多可组合性.Clojure的Rich Hickey说了些什么
每个新的对象类型本质上都与所有编写的代码不兼容
这当然是一个很好的观点.
实用的可组合性还取决于对一小组数据类型的标准化,例如Unix命令使用"制表符分隔的基于行的文本"标准.
后记
Eric Raymond写了一本关于Unix哲学的书,他列出了两个设计原则
来自http://catb.org/~esr/writings/taoup/html/ch01s06.html#id2877537
功能编程中的可组合性可以说将这些原则降低到单个功能的水平.
Mar*_*tos 23
计算机科学中的组合是通过聚合更简单的行为来组装复杂行为的能力.功能分解就是这样的一个例子,其中复杂的功能被分解成更小的易于掌握的功能,并通过顶级功能组装到最终系统中.顶级功能可以说是将这些部分"组合"成整体.
某些概念不容易构成.例如,线程安全的数据结构可以允许安全地插入和删除元素,并且它通过锁定数据结构或其某个子集来实现这一点,这样一个线程就可以执行必要的操作而不会对其进行任何改动 - 并且数据结构已损坏 - 虽然它有效.但是,业务功能可能需要从一个集合中删除一个元素,然后将其插入到另一个集合中,并且需要以原子方式执行整个操作.问题是每个数据结构只发生锁定.您可以安全地从一个元素中删除元素,但是您可能会发现由于某些键违规而无法将其插入另一个元素中.或者您可以尝试将其插入一秒钟,然后将其从第一个中删除,只是发现另一个线程从你的鼻子下偷了它.在意识到你无法完成操作之后,你可以试着把它们放回原来的样子,但却发现逆转因为类似的原因而失败了,你现在处于不确定状态!当然,您可以实现更丰富的锁定方案,该方案涵盖多个数据结构,但只有在每个人都同意新的锁定方案时才有效,并且即使所有操作都在一个单独的操作上,也要承担使用它的负担.数据结构.
因此,互斥式锁定是一种不构成的概念.仅通过聚合较低级别的线程安全操作,您无法实现更高级别的线程安全行为.在这种情况下的解决方案是使用组成的概念,例如STM.
我同意Marcelo Cantos的回答,但我认为它可能比某些读者有更多的背景,这也与函数式编程中的组合特殊性有关.函数式编程函数组成与数学中的函数组成基本相同.在数学中,你可能有一个函数f(x) = x^2和一个函数g(x) = x + 1.组合函数意味着创建一个新函数,其中函数参数赋予"内部"函数,"内部"函数的输出作为"外部"函数的输入.可以写出f外部与g内部的组合f(g(x)).如果你提供的价值1的x话g(1) == 1 + 1 == 2,那么f(g(1)) == f(2) == 2^2 == 4.更一般地说,f(g(x)) == f(x + 1) == (x+1)^2.我用这个描述了构图f(g(x))语法,但数学家通常喜欢不同的语法,(f . g)(x).我认为这是因为它更加清晰,f composed with g它本身就是一个函数,只需要一个参数.
功能程序完全使用组合原语构建.Haskell中的程序可能过于简单化了一个函数,该函数将运行时环境作为参数,并返回对该环境进行某些操作的结果.(对这一陈述进行讨论将需要对monad有所了解.)其他所有内容都是用数学意义上的组合来完成的.