什么是“好好组合”意味着什么?

时间:2010-11-06 03:16:33

标签: programming-languages functional-programming clojure composition

很多次,我遇到过表格的陈述 X确实/不能很好地构成。

我记得我最近读过的几个例子:

  • 宏不构成(上下文:clojure)
  • 锁不构成(上下文:clojure)
  • 命令式编程并不构成......等等。

我想了解可编组性在设计/读/写代码方面的含义?例子很好。

7 个答案:

答案 0 :(得分:10)

“Composing”函数基本上只是意味着将两个或多个函数结合在一起,以创建一个以有用的方式组合其功能的大功能。基本上,您定义一系列函数并将每个函数的结果输入到下一个函数中,最后给出整个过程的结果。 Clojure为你提供了comp功能,你也可以手工完成。

您可以通过创造性方式与其他功能链接的功能通常比您只能在特定条件下调用的功能更有用。例如,如果我们没有last函数并且只有传统的Lisp列表函数,我们可以轻松地将last定义为(def last (comp first reverse))。看看那个 - 我们甚至不需要defn或提及任何参数,因为我们只是将一个函数的结果传递给另一个函数。例如,如果reverse采取了就地修改序列的命令性路线,那么这将不起作用。宏也存在问题,因为您无法将它们传递给compapply等函数。

答案 1 :(得分:3)

编程中的组合意味着从较小的组件中组装更大的组件。

  • 一元函数的组合通过链接更简单的函数来创建更复杂的一元函数。
  • 控制流构造的组合将控制流构造放置在其他控制流构造中。
  • 数据结构的组合将多个更简单的数据结构组合成一个更复杂的数据结构。

理想情况下,组合单元的工作方式与基本单元类似,作为程序员,您无需了解其中的差异。如果事情达不到理想状态,如果某些事情不能很好地构成,那么你编写的程序可能没有其各个部分的(预期)组合行为。

假设我有一些简单的C代码。

void run_with_resource(void) {
  Resource *r = create_resource();
  do_some_work(r);
  destroy_resource(r);
}

C促进关于功能级别的控制流的组成推理。我不必关心do_some_work()中实际发生的事情;我只是通过查看这个小函数来了解每次使用create_resource()在第2行创建资源时,它最终将在第4行被destroy_resource()销毁。

嗯,不太好。如果create_resource()获取一个锁并且destroy_resource()释放它会怎么样?然后我不得不担心do_some_work是否获得相同的锁,这会阻止函数完成。如果do_some_work()调用longjmp()并完全跳过我的函数结束怎么办?直到我知道do_some_work()中发生了什么,我将无法预测我的函数的控制流。我们不再具有组合性:我们不能再将程序分解为多个部分,独立地推理部件,并将我们的结论带回整个程序。这使得设计和调试变得更加困难,这也是人们关心某些事情是否合理的原因。

答案 2 :(得分:2)

组合(在您在功能级别描述的上下文中)通常是将一个功能干净地送入另一个功能而无需中间处理的能力。这样的组合示例在C ++的std :: cout中:

cout<<每个<< item<<链接<<上;

这是一个组合的简单例子,它并不像组合一样“看起来”。

另一个形式更明显构成的例子:

FOO(巴(巴兹()));

Wikipedia Link

组合对于可读性和紧凑性很有用,但是链接大量可能会返回错误代码或垃圾数据的互锁函数可能是危险的(这就是为什么最好最小化错误代码或空返回值。)

如果您的函数使用异常,或者返回null objects,您可以最大限度地减少对错误进行分支(if)的要求,并最大限度地提高代码的组合潜力,而不会产生额外的风险。


对象composition (vs inheritance)是一个单独的问题(而不是你要问的问题,但是它共享名称)。它是导出对象层次结构而不是直接继承的包含之一。

答案 3 :(得分:2)

“为巴克而战” - 构图很好意味着每个组合规则的表现力很高。每个宏都引入了自己的组合规则。每个自定义数据结构都是相同的。函数,尤其是那些使用通用数据结构的函数,其规则要少得多。

作业和其他副作用,特别是wrt并发性有更多规则。

答案 4 :(得分:2)

考虑何时编写函数或方法。您可以创建一组功能来执行特定任务。使用面向对象语言时,您可以围绕您认为系统中的不同实体将执行的操作来聚类您的行为。功能程序通过鼓励作者根据抽象对功能进行分组来脱离这一点。例如,Clojure Ring库包含一组覆盖Web应用程序中的路由的抽象。

Ring是可组合的,其中描述系统中路径的函数(路径)可以分组为更高阶函数(midwhere)。实际上,Clojure是如此动态,以至于可以(并且鼓励您)提出可以在运行时动态创建的路由模式。这是可复制性的本质,而不是提出解决某个问题的模式,而是关注于为某类问题生成解决方案的模式。构建器和代码生成器只是函数式编程中常用的两种模式。函数编程是生成其他模式(等等)的模式的艺术。

这个想法是在最基本的层面解决问题,然后提出解决问题的最低级别函数的模式或组。一旦你开始看到最低级别的模式,你就会发现构图。当人们发现功能组中的二阶模式时,他们可能会开始看到第三级。等等...

答案 5 :(得分:1)

在clojure的上下文中,this comment解决了可组合性的某些方面。一般来说,当系统单元做好一件事,不要求其他单位理解其内部结构,避免副作用,接受并返回系统的普遍数据结构时,似乎会出现这种情况。所有上述内容都可以在M2tM的C ++示例中看到。

答案 6 :(得分:1)

可组合性,应用于函数,意味着函数更小,定义更好,因此很容易集成到其他函数中(我在书中看到了“clojure的快乐”中的这个想法)

这个概念可以应用于其他被认为是由其他东西组成的东西。

可组合性的目的 是重用。例如,一个功能良好的构建(可组合)更容易重用

不是那么可以组合,因为你不能将它们作为参数传递

lock 是垃圾,因为你无法真正给它们命名(很好地定义它们)或重用它们。你只是在现场进行

命令式语言不是可组合的,因为(其中一些,至少)没有闭包。如果你想把功能作为参数传递,你就搞砸了。你必须建立一个对象并传递它; 免责声明:这个最后一个想法我并不完全相信是真的,因此在将其视为理所当然之前进行更多的研究

命令式语言的另一个想法是,它们不能很好地构成,因为它们暗示状态(来自wikipedia knowledgebase :)“命令式编程 - 描述计算改变程序状态的语句术语“)。

状态组成不好,因为虽然你在输入中给出了一个特定的“东西”,但“某些东西”会根据它的状态产生一个输出。不同的内部状态,不同的行为。因此,你可以告诉你预期会发生什么。

对于状态,你很大程度上依赖于了解对象的当前状态...如果你想预测它的行为。更多的东西要留在你的脑海里,不那么可组合(记得定义明确的?或“和简单”,如“易于使用”?)< / p>

ps:想学习clojure,是吧?调查......?对你有益 ! :P