数学中的正式语言与编程中的更多口语化语言的混合使这些对话变得困难。您在这里处理两个上下文加载的词:“composable”和“function”。
函数组合——在数学中
“函数”A → B 的数学概念是从某个集合 A 到某个集合 B 的映射,“函数组合”是由 ∘ 表示的特定操作。对于某些f: A → B 和g: B → C,g∘f 是一个函数A → C,使得(g∘f)(x) = g(f(x)) 对于x 中的所有A。如果两个函数的域/共域以这种方式匹配(换句话说,这样的一对函数“可以组合”),则为任何两个函数定义此组合,我们通过声明“函数是可组合的”来描述这一点。
可组合性——在编程中
作为一个定性术语,我们经常在软件中使用“可组合性”来描述一组组合通过组合小组合构建大事物的能力。从这个意义上说,程序员将函数(作为一个整体)描述为“非常可组合”,因为函数可以(并且,在像 Haskell 这样的纯函数式语言中,确实可以)构成整个程序的大小。
在软件中,我们还看到“可组合”一词更人性化的用法,它往往与“模块化”相关联。当组件是无状态的、关注点是分离的并且 API 的表面积较小时,编写程序而不会出错就更容易了。我们称赞这种设计的组件是“可组合的”——不仅因为它们可以组合,还因为它们容易正确组合 em>。
函数——在编程中
我将使用稍微过时的术语“子程序”,因为我不知道用我们这个时代的说法来讨论这个问题的好方法。如果一个子例程不做任何 IO(并且总是停止,并且不抛出......),那么它实现(或“是”)数学意义上的“函数”。 IO 子例程与函数有表面上的相似之处,因为它们可能具有输入和输出值,但相似之处仅止于此。我们最初讨论过的关于“功能组合”的任何对话都不适用。
这里是我们遇到最棘手的语言困难的地方,因为“函数”这个词已经普遍用于指代任何子例程,甚至是那些执行 IO 的子例程。 FP 爱好者倾向于反对这一点——人们会说“如果它有 IO,它就不是一个函数”——但这场流行之战已经失败,现在没有回头路了。在大多数编程上下文中,所有子程序都称为“函数”,区分满足数学定义的函数的唯一方法是称它们为“纯函数”。
确定了这些定义后,让我们来解决您的问题:
“一个可组合的函数必须有参数和返回值?”
关于这个问题,有一些无聊的事情需要指出。首先,Scala 中的每个函数在技术上都有一个返回类型。如果该类型是Unit,为简洁起见可以省略,但它仍然是返回类型。
并且可以将零参数(0-arg)函数简单地转换为具有参数的等效函数。所以真的,这没关系。如果你需要编写带参数的函数,而f 没有参数,你可以写_ => f。
"这个函数会有副作用吗?"
只是语义上的争吵。在 Scala 的上下文中,最合适的说法是它是一个Function(或者在技术上可能是一个“方法”,取决于它在哪里定义),但由于副作用,它不是一个纯函数.
“我们仍然认为它是‘可组合的’吗?”
有点。所有这些东西仍然以一种相当普遍的方式“结合在一起”,所以是的,它们确实在软件意义上构成。尽管纯函数比不纯函数组合得更好。并且函数组合的数学概念不适用于非纯函数的子程序。
最后,如果您想知道它们是否在 Scala 中使用 Function1 上的 compose 方法编写,您不需要 Stack Overflow;问问编译器就行了。