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

5.5.1 Lambda 函数

 
阅读更多

5.5.1 Lambda 函数

在 F# 中,lambda 函数创建的函数,与一般使用 let 绑定声明的相同。在 C# 中,没有任何内置的函数概念,所以,我们既可以使用方法,也可以使用委托。写的 lambda 函数,被转换为委托或表达式树 (在“C# 中从委托到函数"边栏中,可以找到有关表达式树的详细信息),但是,在 C# 中,不能使用 lambda 函数声明普通方法。委托可以像任何其他值一样使用,所以,可以把它们作为参数值传递给其他的方法,这意味着,我们可以用它们在 C# 中写高阶函数。让我们首先看一个 F# Interactive 会话,然后,在 C# 中,写出类似的代码。清单 5.15 显示了如何在 F# 中,可以写一个函数,使用 let 绑定和 lambda 函数语法。

Listing 5.15 Using lambda functions and let bindings (F# Interactive)

> let square1(a) = a * a;;
val square1 : int –> int

> let square2 = fun a -> a * a;;
val square2 : int –> int

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

> add 2 3
val it : int = 5

我们首先写一个简单的函数,叫 square1,计算给定的数的平方,我们之前曾经见过几次,在以相同的方式。我们输入它之后,F# 打印其签名(值类型),告诉我们它取一个整数,并返回一个整数。接下来,我们声明另一个值,叫 square2,使用 lambda 符号,初始化为一个函数。如你所见,通过看输出, 两个声明是等价的。最后,我们声明另一个值,它显示了有两个参数的 lambda 函数的语法。看到这些示例后,你也许可以重写任意 F# 函数,使用有 let 绑定的 lambda 表示法,反之亦然。

现在,让我们看看如何写同样的东西,在 C# 中使用 lambda 函数:

Func square =
a => a * a;
Func add =
(a, b) => a + b;

我们使用一个委托类型,叫 Func,这在 .NET 3.5 中是可用的。此委托代表一个函数,其类型参数值指定参数的类型和返回的类型。从技术角度讲,Func 不是单个委托,而是由数目不同的类型参数重载的一族委托。每个代表函数有不同的参数数目。下面是 C# 和 F# 语法之间的显著差异:

■ F# lambda 表达式声明以 fun 关键字开始。

■ 在 C# 中,在括号内指定多个参数,用逗号分隔。在 F# 中,用空格分隔参数。

■ 在 C# 中,声明一个委托值时,必须显式指定类型。

C# 中从委托到函数

如前所述,在 C# 中的函数使用委托来表示,特别是新的 Func 委托族。在某种意义上,lambda 函数与此委托是革命性的变化,为 C# 增加了函数编程,但它也可以也被看作是C# 中已有功能的自然进化。本书通常取前一种看法,但接下来我们将看一下进化的表现。

在 C# 的第一个版本中,就已经有了委托,但没有泛型,我们不得不为返回和参数类型的每个组合单独声明委托。当创建委托时,我们还必须在命名方法内写代码,所以,写出的代码可能像这样:

delegate int FuncIntInt(int a, int b);
FuncIntInt add = new FuncIntInt(Add);

该代码假定有一个 Add 方法,有两个整数参数,返回一个整数类型。C# 2.0 向前迈出一大步,增加了泛型,所以,我们能声明泛型委托,像 Func (尽管它还并不包括在基类库中),创建它们,而使用新的匿名方法功能,而不要写命名方法了:

delegate R Func(T1 arg1, T2 arg2);
Func add = delegate(int a, int b) { return a + b; }

最后,.NET 3.5 和 C# 3.0 附带了几个其他的改变,Func 委托已添加到系统库中,C# 添加了 lambda 表达式,使我们以更简洁的方式写出相同的代码:

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

Lambda 表达式具有另一个有趣的特点:它们可以转换为表达式树(expression trees),当我们将它们声明为 Expression (表达式)类型时。这样,就可以把 lambda 表达式的代码当作数据看待,并获得 lambda 表达式的源代码一些表示。这对于使用 LINQ 处理数据库是重要的,但现在,对我们来说还不是主要功能。此外,因为这一功能,我们在声明 lambda 表达式时,不能使用 var 关键字,因为编译器需要决定是否把它编译成代理 (Func),或存储表达式树 (Expression)。

在 C# 中的 Func 委托和 lambda 表达式类似于 F# 中的函数,但是,F# 从一开始就有函数,所以,它几乎不需要为委托。它支持委托,主要是用于互操作性的原因,但是,更多的时候可能不会使用它们。

已经看了 F# 和 C# 中的几个 lambda 函数的例子,但还有几个重要的事情要去探索。

类型注释、动作和语句块

在前面的示例中,我们没有显式指定的参数类型。这在 F# 中是正常的行为,因为,其类型推断能力非常强大,在前面的示例中,它有足够的线索推断出类型。在 C# 中的情况,是另一种有趣的的方式:

Func toStr1 = num => num.ToString();
Func toStr2 = (int num) => num.ToString();

这两行显示相同的代码,唯一区别在于,第二行显式指定 num 参数的类型。这两行都是正确的,那么,C# 如何知第一行中的道 num 的类型的呢?答案是,它使用类型来自变量的声明。它知道,Func < int, string> 是一个委托,取一个整数作为参数值,因此,它推断 num 的类型应该为整数。

在 C# 中,很少需要显式参数键入。无论如何,不能使用 var 关键字声明 lambda 函数,因此,C# 通常能够推断出类型。一个例外情况是,我们将使用 lambda 函数作为一个参数值,给特定的泛型方法。即使在 F# 中,我们偶尔也可能需要给编译器以更多信息,即用类型注释(type annotations)。清单 5.16 显示了有类型注释的 lambda 函数,显式声明其参数的类型。

Listing 5.16 Advanced lambda functions (F# Interactive, C#)

// F# version of the code (using F# Interactive)
> let sayHello =
(fun (str:string) –>
let msg = str.Insert(0, "Hello ")
Console.WriteLine(msg)
)
val sayHello : string –> unit

// C# version of the code
Action sayHello =
str => {
var msg = str.Insert(0, "Hello ");
Console.WriteLine(msg);
};

这个示例显示了几个有趣的事情。第一个是在 F# 版本中使用类型注释。在 lambda 函数中的类型注释的语法与 F# 代码中的其他任何地方都相同。在这种情况下我们为什么要使用它的原因是,我们将调用值 str 的实例方法 Insert,它没给编译器足够的信息来确定值的类型。

另一个值得注意的事情是,lambda 函数的主体不只是一个表达式。在 F# 中,我们添加了一个 let 绑定,把整个 lambda 函数括在括号中。在 C# 版本中,我们添加了一个变量声明,改变语法为使用语句块(statement block)。语句块意味着,lambda 函数体被括在大括号内,它允许我们写在主体内多个语句。要从 lambda 函数返回结果,就使用语句块,用 return 关键字,就如同从方法返回的结果一样。

在此示例中,这个 lambda 函数不返回结果。在 F# 中,unit 是一个普通的类型,推导出的函数签名是 string -> unit。这是一个普通的 F# 函数,原则上,返回 unit 值(即,什么也没有)作为结果。在 C# 中,我们不能写成 Func<string, void>,因为,void 不是一个真正的类型。为此,C# 有另外一族委托类型,称为动作(Action),表示 lambda 函数没有返回类型。Action 和 Func 委托非常有用,对应于 F# 中的函数类型,因此,让我们更详细地看一下函数值的类型。

分享到:
评论

相关推荐

    C++ Primer中文版(第5版)李普曼 等著 pdf 1/3

     14.8.1 lambda是函数对象 507  14.8.2 标准库定义的函数对象 509  14.8.3 可调用对象与function 511  14.9 重载、类型转换与运算符 514  14.9.1 类型转换运算符 514  14.9.2 避免有二义性的类型转换 517  ...

    C++Primer(第5版 )中文版(美)李普曼等著.part2.rar

     14.8.1 lambda是函数对象 507  14.8.2 标准库定义的函数对象 509  14.8.3 可调用对象与function 511  14.9 重载、类型转换与运算符 514  14.9.1 类型转换运算符 514  14.9.2 避免有二义性的类型转换 517  ...

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

     11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   11.8.1 全局变量与局部变量   11.8.2 globa语句   11.8.3 ...

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

     11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   11.8.1 全局变量与局部变量   11.8.2 globa语句   11.8.3 ...

    Python核心编程第二版(ok)

     11.7.1 匿名函数与lambda   11.7.2 内建函数apply().cfilter().cmap().creduce()   11.7.3 偏函数应用   11.8 变量作用域   11.8.1 全局变量与局部变量   11.8.2 globa语句   11.8.3 ...

    Python核心编程第二版

     11.7.1 匿名函数与lambda   11.7.2 内建函数apply()、filter()、map()、reduce()   11.7.3 偏函数应用   11.8 变量作用域   11.8.1 全局变量与局部变量   11.8.2 globa语句   11.8.3 ...

    JavaScript王者归来part.1 总数2

     6.5.1 动态创建函数--一个利用Function实现Lambda算子的例子   6.5.2 模式--函数工厂及其实例   6.6 总结   第7章 对象  7.1 什么是对象   7.2 对象的属性和方法   7.2.1 对象的内置属性   7.2.2 为...

    Visual C++ 2010入门经典(第5版)--源代码及课后练习答案

    5.5.1 接受数量可变实参的函数 241 5.5.2 main( )的实参 242 5.6 小结 243 5.7 练习 243 5.8 本章主要内容 244 第6章 程序结构(2) 245 6.1 函数指针 245 6.1.1 声明函数指针 246 6.1.2 函数指针作为实参 249...

    ASP.NET3.5从入门到精通

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET 的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

    ASP.NET 3.5 开发大全11-15

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

    ASP.NET 3.5 开发大全

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

    ASP.NET 3.5 开发大全1-5

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

    ASP.NET 3.5 开发大全word课件

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

    ASPNET35开发大全第一章

    3.2.4 构造函数和析构函数 3.3 对象的生命周期 3.3.1 类成员的访问 3.3.2 类的类型 3.3.3 .NET的垃圾回收机制 3.4 使用命名空间 3.4.1 为什么要用命名空间 3.4.2 创建命名空间 3.4.3 分层设计中使用命名空间 3.5 类...

Global site tag (gtag.js) - Google Analytics