F#使用多个输入参数进行函数组合

7

我是F#的新手,最近发现了函数组合运算符 >>

我理解基本原理,使得像这样的东西成为可能...

let Add1ToNum x = x +1
let Mul2ToNum y = y * 2
let FuncComp = Add1ToNum >> Mul2ToNum

然而在有多个函数具有不同数量的输入参数时,如何处理组合呢?例如,我想要能够执行以下操作...

let AddNums (x,y) = x+y
let MulNums (x,y) = x*y
let FuncComp = Add1 >> Mul2

显然这是不可行的,因为AddNums返回一个int,而MulNums期望得到一个元组。

是否有某种语法可以实现这一点,或者如果我想使用函数组合,是否必须始终执行某种中间函数来转换值?

对此有任何建议将不胜感激。


4
我不太理解你使用 AddNum >> MulNums 的意图,因为 AddNums 的输出是一个单独的数字,因此不符合 MulNums 的有效输入格式,这意味着这两个函数无法组合使用。 - Yin Zhu
3个回答

10
正如Yin和codekaizen指出的那样,您无法组合这两个函数来创建一个函数,该函数将输入传递给第一个函数,然后将此调用的输出传递给第二个函数(即使用>>运算符)。使用图表,您不能执行以下操作:
     +---------+    +---------+
 --->| AddNums |--->| MulNums |--->
     +---------+    +---------+

一种选择是更改函数并指定其中一个参数,以便可以组合这些函数。codekaizen的示例使用了这种方式,如果您使用柯里化而不是元组参数,则也可以编写如下代码:

let AddNums x y = x + y  
let MulNums x y = x * y  
let FuncComp = (AddNums 1) >> (MulNums 2)

另一种组合函数的选项是创建一个接受多个输入的函数,将两个数字传递给第一个函数,然后使用结果和原始输入中的另一个数字调用第二个函数。使用图示:

 -----------------\
 --->+---------+   \+---------+
 --->| AddNums |--->| MulNums |--->
     +---------+    +---------+

如果您需要类似的东西,最好的选择是直接编写,因为这可能不是一个经常重复的模式。直接编写很容易(使用柯里化变体):
let AddNums x y = x + y  
let MulNums x y = x * y  
let FuncComp x y z = AddNums z y |> (MulNums z)

如果你想更一般地编写类似的东西(或者只是出于好奇),你可以像这样编写代码(这次使用函数的元组版本)。&&&操作符受到Arrows的启发。
let AddNums (x,y) = x + y 
let MulNums (x,y) = x * y  

let (&&&) f g (a, b) = (f a, g b)
let FuncComp = (AddNums &&& id) >> MulNums

// First two numbers are added, result is multiplied by the third one
FuncComp ((9, 12), 2) // Gives '42'

谢谢Thomas,这讲得非常清楚! - Mark Pearl
@Tomas:恐怕你使用了错误的箭头运算符。应该是( *** );) - Gus
让 ( *** ) f g (a, b) = (f a, g b) 让 ( &&& ) f g a = (f a, g a) - ben

4
另一种选择是让堆栈到堆栈的函数类似于逆波兰计算器。例如:
let bin f = function a :: b :: t -> f b a :: t
let add = bin (+)
let mul = bin (*)

也许需要一个将字面量推入堆栈的函数:
let lit n t = n :: t

然后就是纯粹的组合:
> (lit 9 >> lit 12 >> add >> lit 2 >> mul) []
42

您甚至可以添加堆栈重排功能:

let drop = function _ :: t -> t
let dup  = function x :: t -> x :: x :: t
let swap = function x :: y :: t -> y :: x :: t

并且做这样的事情:

let square = dup >> mul
let cube = dup >> dup >> mul >> mul
let negate = lit -1 >> mul

这只是一次疯狂的实验!

(另见http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx)


2
正如Yin指出的,当组合时,您的类型不匹配。`AddNums`和`MulNums`的类型为`int * int -> int`,因此您不能期望将其中一个的输出插入到另一个的输入中。
我注意到您的最后一行是`let FuncComp = Add1 >> Mul2`,这可能是笔误,但可以让您了解如何“绑定”接受元组的函数,以便它们可以组合:
let Add1 x = AddNums(x, 1)
let Mul2 x = MulNums(x, 2)
let FuncComp = Add1 >> Mul2 

运行时:

FuncComp(1);;

val it : int = 4


1
感谢codekaizen,我在问题中指出了我知道一个函数的输出与另一个函数的输入不同,并且函数组合运算符不起作用,但我正在寻找可能的替代方案的解释。 - Mark Pearl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接