并置函数
Last updated
Last updated
其实很多函数没有必要完全自定义的去实现。让我们先来看一个使用Haskell实现的在文本中查找单一单词的例子。第一行定义了一个unique_words
函数,在第二行中传入一个字符串:
Wow,就是这么简单!这里不对Haskell的语法做过多的解释,让我们来看一下代码。其定义了一个unique_words
的函数,该函数对其传入的参数进行了一系列的处理。首先,使用map toLower
将所有字符都小写化。然后,将句子用逗号进行分割,比如"foo bar baz"
就会已变成["foo", "bar","baz"]
。接下来,将单词列表进行排序。这样,["a", "b", "a"]
就会变为["a", "a", "b"]
。现在,使用group
函数,其会将相同的词组放到一个列表中,也就是["a", "a", "b"]
成为[ ["a", "a"], ["b"] ]
。现在就差不多快完事了,接下来就让我们数一下列表中一共有多少个组,这个工作由length
函数完成。
多么完美的编程方式呀!我们可以从右往左看,来了解这段代码是如何工作的。这里我就不需要关心每个细节是如何进行实现(除非其性能很差,或者有Bug)。
我们不是来赞美Haskell的,而是来提升我们自己C++技能的,这样的方式在C++中同样奏效。本节的例子会展示如何使用Lambda表达式来模仿并置函数。
本节中定义了一些函数对象,并将它们串联起来,也就是将一个函数的输出作为另一个函数的输入,以此类推。为了很好的展示这个例子,我们编写了一些串联辅助函数:
包含必要的头文件
然后,我们实现一个辅助函数concat
,其可以去任意多的参数。这些参数都是函数,比如f,g和h。并且一个函数的结果是另一个函数的输入,可以写成f(g(h(...)))
:
现在,代码就会变有些复杂了。当用户提供函数f,g和h时,我们现将其转换为f( concat(g,h))
,然后再是f(g(concat(h)))
,类似这样进行递归,直到得到f(g(h(...)))
为止。用户提供的这些函数都可以由Lambda表达式进行捕获,并且Lambda表达式将在之后获得相应的参数p,然后前向执行这些函数f(g(h(p)))
。这个Lambda表达式就是我们要返回的。if constexpr
结构会检查在递归步骤中,当前函数是否串联了多个函数:
当我们到达递归的末尾,编译器会选择if constexpr
的另一分支。这个例子中,我们只是返回函数t
,因为其传入的只有参数了:
现在,让我们使用刚创建的函数连接器对函数进行串联。我们先在主函数的起始位置定义两个简单的函数对象:
现在,来串联他们。这里我们将两个乘法器函数和一个STL函数std::plus<int>
放在一起,STL的这个函数可以接受两个参数,并返回其加和。这样我们就得到了函数twice(thrice(plus( a, b )))
:
我们来应用一下。combined
函数现在看起来和一般函数一样,并且编译器会将这些函数连接在一起,且不产生任何不必要的开销:
编译运行这个例子就会得到如下的结果,和我们的期望一致,因为2 * 3 * (2 + 3)
为30:
concat
函数是本节的重点。其函数体看起来非常的复杂,因为其要对另一个Lambda表达式传过来ts
参数包进行解析,concat
会递归多次调用自己,每次调用参数都会减少:
让我们写一个简单点的版本,这次串联了三个函数:
这个例子看起来应该很简单了吧。返回的Lambda表达式可以对f,g和h函数进行捕获。这个Lambda表达式可以接受任意多的参数传入,然后在调用f,g和h函数。我们先定义auto combined (concat(f, g, h))
,并在之后传入两个参数,例如combined(2, 3)
,这里的2和3就为concat
函数的参数包。
看起来很复杂,但concat
却很通用,有别与f(g(h( params... )))
式的串联。我们完成的是f(concat(g, h))(params...)
的串联,f(g(concat(h)))(params...)
为其下一次递归调用的结果,最终会的结果为f(g(h( params...)))
。