`
tianshibaijia
  • 浏览: 1124752 次
文章分类
社区版块
存档分类
最新评论

5.5.3 多参数值的函数

 
阅读更多

5.5.3 多参数值的函数

让我们回顾一下,写一个函数,能有哪些选项。在 F# 中,当写具有多个参数值的函数时,我们可以使用元组。我们下一个示例显示了一个函数,以这种风格加两个整数。我们将使用 lambda 函数的语法,但在 F# 中,使用简单的 let 绑定,也可以得到相同的结果:

> let add = fun (a, b) -> a + b;;
val add : int * int -> int

通过看类型签名,可以看到,该函数取一个参数值,这是一个元组的形式 (int * int),返回类型为 int。对应的 C# 的 lambda 函数,写成这样的形式:

Func<int, int, int> add =
(a, b) => a + b

Func < int、 int、 int > 委托表示一个方法,有两个 int 类型的参数值,并返回一个 int 类型,所以,这类似于使用元组写的 F# 版本。调用这个函数时,也可以看到这种相似性:

let n = add(39, 44)
var n = add(39, 44)

调用以元组作为参数值的 F# 函数(第一行),与调用 C# Func 委托(第二行)的语法是相同的。现在,让我们写相同的代码,在 F# 中,使用传统的 F# 样式,写有多个参数值的函数:

> let add = fun a b -> a + b;;
val add : int -> int -> int

令人惊讶的是,这与我们早先看到的在返回一个函数时的签名相同。可以读作, int-> (int-> int)。这就是一个函数,取第一个参数给这个加法,并返回一个函数。那么,结果是一个函数,取第二个参数值。我们可以重写这段代码,以这种方式,使用两个 lambda 函数,一个嵌套在另一个中:

> let add = fun a -> fun b -> a + b;;
val add : int -> int -> int

如果这是你第一次见到这种概念,它似乎很奇怪。一个函数返回另外一个函数,如何可以和函数,取另一个参数值并返回一个整数相同呢?有一个参数的函数如何可以和有两个参数的函数相同呢?不要太担心,如果不能马上理解——我们承诺,最终会理解的。在看过更多的示例之后,可能想转回到这一节,你就会更容易领会这个概念。

有多个元素的元组

如果要创建具有两个以上元素的元组,在 C# 中使用 .NET 4.0,可以使用重载的 Tuple 类,它提供重载表示的元组包含的元素从一个到八个。我们在第 3 章中实现的 C# 类有更多的限制,只支持仅 2 个元素。我们实现的重载,无论有多少个,总是会有一些限制。

不过,有一个方法可以克服这种限制。让我们看一下,我们如何可以使用来自第 3 章的 Tuple<A, B> 类型,表示 F# 的类型 int * string * bool。这个解决方案很简单,要存储比我们的元组类型支持的更多的元素,可以嵌套元组:

Tuple<int, Tuple<string, bool>> tup = (...);

当我们像这样声明一个变量时,它会携带三个值。要得到整型值,我们可以写成 tup.Item1;访问字符串值,写成 tup.tem2.Item1;最后,布尔值存储在 tup.Item2.Item2 中。

这是类似于嵌套函数,如 F# 类型 int -> (string -> bool)。在元组和函数之间有不同。此处所示的函数类型,与 int -> string –> bool 是同样的事情,而 F# 有三个元素的元组 (int * string * bool), 是不同于嵌套的元组类型,如 int * (string * bool)。

你可能想知道是否存在在 C# 3.0 中重写我们前面的示例的方法——确实可以。不要创建 Func<int, int, int> 类型的委托,可以创建一个 Func<int, Func<int, int>> 类型的委托,这更接近于 F# 函数的理解,签名为 int-> (int-> int):

Func<int, Func<int, int>> add =
a => b => a + b;
int n = add(39)(44);

写这个声明,用了两个 lambda 函数,就像我们前面的 F# 示例。当用此委托加数字时,我们必须调用第一个委托,它返回另一个委托;然后,调用第二个委托。在 F# 中,这是完全正常的、使用函数的方法,编译器会优化它,使之更有效率。

这是所有很有趣,但是,你可能会想,函数以这种方式的分离点是什么呢?原来是惊人的强大。

偏函数应用(PARTIAL FUNCTION APPLICATION)

要显示对函数的新理解是有用的情况,让我们把注意力返回到列表。假设我们有一个数字列表,我们想要为列表中的每个数字加上 10。在 F# 中,可使用 List.map 函数完成 ;在 C# 中,可以使用来自 LINQ 的 Select 方法:

list.Select(n => n + 10)
List.map (fun n -> n + 10) list

这已经相当简单了,但是还可以更简洁,如果我们已经有了来自前面示例中的 add 函数。List.map 期望作为第一个参数值的函数是 int -> int 类型。即,函数取一个整数作为参数值,并返回另一个整数。我们可以使用的技术称为部分偏函数应用(partial function application ):

> let add a b = a + b;;
val add : int -> int –> int

> let addTen = add 10;;
val addTen : (int -> int)

> List.map addTen [ 1 .. 10 ];;
val it : int list = [11; 12; 13; 14; 15; 16; 17; 18; 19; 20]

> List.map (add 10) [ 1 .. 10 ];;
val it : int list = [11; 12; 13; 14; 15; 16; 17; 18; 19; 20]

函数 add 的类型是 int -> int-> int。因为我们现在知道,它实际上意味着,该函数取一个整数并返回一个函数,因此,可以创建一个函数 addTen,把 10 加到给定的参数值上,通过调用 add,只有第一个参数值。然后,可以使用此函数作为参数值,给 List.map 函数。这有时很有用,但更重要的是,我们可以直接使用偏函数应用,在为 List.map 函数指定第一个参数值时。

函数 add 的类型是 int-> (int-> int),通过用一个数字作为参数值调用它,得到的结果类型是 int-> int,这正是 List.map 函数所需要的。当然,我们可以在 C# 中写相同的代码,如果使用嵌套的 lambda 函数声明 add 函数:

Func<int, Func<int, int>> add =
a => b => a + b;

list.Select(add(10));

正如我们在 F# 版本中看到的,调用 add 委托,得到结果类型 Func<int, int>,这与 Select 方法兼容。在 C# 中,使用具有多个参数值的 Func 委托,并使用另一个 lambda 函数为 Select 方法指定参数值,是更方便的,因为,语言支持更好。

注意

在使用偏函数应用时,会听到一个术语科里化(currying) 。它指的是,转换有多个参数值作(比如元组)的函数,到有第一个参数值的函数,并返回一个函数,有其余的参数,等等。例如,类型 int -> int-> int 的函数是(int * int)-> int 类型函数的科里化形式。那么,偏函数应用就是不指定所有参数值的科里化函数的使用。

正如我们已经提到,F# 中选择正确的样式可能很难。使用元组写的代码有时更容易读取大量的参数值,但它不能于偏函数应用。在这本书的其余部分,我们将使用感觉更适合各种情况的风格,这样,就可以获得的直观地理解哪个更好。最重要的是,我们使用元组,是在使代码更具可读性的情况下,和允许偏函数应用的风格,这种情况为我们提供了明显的好处。在下一章讨论高阶函数时,我们将会看到有关后者的很多例子。

分享到:
评论

相关推荐

    Excel公式与函数大辞典.宋翔(带书签高清文字版).pdf

    本书从函数功能、函数格式、参数说明、注意事项、Excel 版本提醒、案例应用、交叉参考7 个方面,全面、细致地介绍了Excel 2016/2013/2010/2007/2003 中公式和函数的使用方法、实际应用和操作技巧。最后3 章还将公式...

    精通WindowsAPI 函数 接口 编程实例

    6.2.1 创建进程、获取进程相关信息、获取启动参数 153 6.2.2 编写控制台程序和图形用户界面应用程序 158 6.2.3 获取和设置环境变量 158 6.3 线程、纤程 162 6.3.1 创建线程、退出线程、获取线程信息 162 ...

    精通Windows.API-函数、接口、编程实例.pdf

    6.2.1 创建进程、获取进程相关信息、获取启动参数 153 6.2.2 编写控制台程序和图形用户界面应用程序 158 6.2.3 获取和设置环境变量 158 6.3 线程、纤程 162 6.3.1 创建线程、退出线程、获取线程信息 162 ...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    8.3 函数的参数和函数的值 100 8.3.1 形式参数和实际参数 101 8.3.2 函数的返回值 102 8.4 函数的调用 106 8.4.1 函数调用的一般形式 106 8.4.2 函数调用的方式 106 8.4.3 被调用函数的声明和函数原型 107 8.5 函数...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    8.3 函数的参数和函数的值 100 8.3.1 形式参数和实际参数 101 8.3.2 函数的返回值 102 8.4 函数的调用 106 8.4.1 函数调用的一般形式 106 8.4.2 函数调用的方式 106 8.4.3 被调用函数的声明和函数原型 107 8.5 函数...

    Python核心编程(第二版).pdf (压缩包分2部分,第二部分)

     11.6.3 调用带有可变长参数对象函数   11.7 函数式编程   11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   ...

    Python核心编程(第二版).pdf (压缩包分2部分,第一部分)

     11.6.3 调用带有可变长参数对象函数   11.7 函数式编程   11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   ...

    Python核心编程第二版(ok)

     11.6.3 调用带有可变长参数对象函数   11.7 函数式编程   11.7.1 匿名函数与lambda   11.7.2 内建函数apply().cfilter().cmap().creduce()   11.7.3 偏函数应用   11.8 变量作用域   ...

    Python核心编程第二版

     11.6.3 调用带有可变长参数对象函数   11.7 函数式编程   11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   ...

    程序员的SQL金典6-8

     5.5.3 Oracle中的独有函数 第6章 索引与约束  6.1 索引  6.2 约束  6.2.1 非空约束  6.2.2 唯一约束  6.2.3 CHECK约束  6.2.4 主键约束  6.2.5 外键约束 第7章 表连接  7.1 表连接简介  7.2 内连接...

Global site tag (gtag.js) - Google Analytics