8.2.3.2 在闭包中捕捉引用单元

现在,我们可以编写代码,捕获在闭包中使用引用单元创建的可变状态。清单 8.9 显示了可配置收入检查的 F# 版本。我们创建了 createIncomeTests 函数,返回有两个函数的元组:第一个函数改变所需的最低收入,第二个函数测试函数自身。

清单 8.9 使用闭包测试可配置收入 (F# Interactive)

> let createIncomeTest () =

let minimalIncome= ref 30000   [1]

(fun (newMinimal)–>

minimalIncome := newMinimal),  <-- 设置新的最低收入

(fun (client)–>

client.Income < (!minimalIncome))  <-- 使用当前最低收入测试客户

;;

val createIncomeTest : unit -> (int-> unit) * (Client -> bool)  [2]

> let setMinimalIncome, testIncome =createIncomeTest();;   [3]

val testIncome : (Client -> bool)

val setMinimalIncome : (int -> unit)

> let tests = [ testIncome; (* moretests... *) ];;

val tests : (Client -> bool) list

首先,我们看一下createIncomeTest 函数的签名[2],它没有任何参数,返回结果为函数元组。在函数体中,首先创建一个可变引用单元,并初始化成默认的最低收入[1]。返回的函数元组是两个lambda 函数,都用了minimalIncome 值。第一个函数(签名为int -> unit)的参数为新的收入,并修改引用单元;第二个函数比较客户的收入与保存在引用单元中的当前值,用来检查客户的函数通常签名(Client -> bool)。

当我们后来调用createIncomeTest 时[3],结果得到两个函数。我们只创建一个引用单元,因此,它由两个函数的闭包所共享;我们可以用 setMinimalIncome 改变 testIncome 函数所需的最低收入。

我们现在看一下,在这个 F# 版本和前面讨论的用 C# 实现的命令模式之间的类似性。在 F# 中,状态由闭包自动捕获,而在 C# 中,状态是封装在显式的类中。从某种意义上讲,函数元组和闭包对应于面向对象模式的接收者对象。正如我们在清单 8.7 所见到的,F# 编译器通过生成类似于我们在 C# 中显式写的.NET 代码,处理闭包。由 .NET 使用的中间语言(IL),不直接支持闭包,但它有保存状态的类。

清单 8.10 完成了这个例子,演示如何使用 setMinimalIncome 函数,修改检查。这个例子假定 testClient 函数现在使用清单 8.9 中声明的检查集合。为了在 F# Interactive 中完成,需要选中并计算声明 tests 的值绑定,然后,计算 testClient 函数,以便它引用先前计算的集合。

清单 8.10 在检查期间改变最低收入 (F# Interactive)

> testClient(john);;

Client: John Doe

Offer a loan: YES (issues = 1)

> setMinimalIncome(45000);;

val it : unit = ()

> testClient(john);;

Client: John Doe

Offer a loan: NO (issues = 2)

正如在 C# 版本中一样,我们首先使用初始的测试来检查客户(传递的客户),然后,修改其中一个测试所需的收入。改变之后,客户不再满足条件,结果是否定的。

C#中的闭包

在上一节,我们使用 C# 写面向对象的代码,用 F# 写函数式代码,因为我们想演示概念之间的关系,闭包类似于对象,特别是命令设计模式中的接收者对象。

闭包是 lambda 函数的核心,C#3.0 中的 lambda 表达式也支持创建闭包的语法;在 C# 2 中,是以匿名方法的形式表现的。下面的例子显示如何创建一个函数,经过多次调用,将返回一个从零开始的数字序列:

Func<int> CreateCounter() {

int num = 0;

return () => { return num++;};

}

变量 num 由闭包捕获,每次调用,返回的函数增加它的值。在 C# 中,变量默认是可变的,所以,像这样改变捕获变量的值,要特别小心。通常引起混乱的根源是捕获了 for 循环中的循环变量。假设在多次迭代中捕获这个变量,到循环结束时,所有创建的闭包将包含相同的值,因为我们只使用了一个变量。

在本节,我们讨论了面向对象模式和相关的函数技术。在某些情况下,我们使用函数,而不是只有一个方法的接口。接下来,我们将看一个例子,行为虽然很简单,但不能只用一个函数来描述时,我们该如何做。

时间: 2024-10-05 23:02:08

8.2.3.2 在闭包中捕捉引用单元的相关文章

闭包中的 内存泄漏

内存泄漏 如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁. 1 function assignHandler(){ 2 var element = document.getElementById("someElement"); 3 element.onclick = function(){ 4 alert(element.id); 5 } 6 } 而这个闭包则又创建另一个循环引用.由于匿名函数保存了一个对 assignHandler()的活动对象的引用,因此

闭包中的 this 对象

关于this对象 在闭包中使用this对象也可能会导致一些问题.this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window, function createFunction(){ alert(this); }; createFunction(); // window 而当函数被作为某个对象的方法调用时,this等于那个对象.不过,匿名函数的执行环节具有全局性,因此其this对象通常指向 window. 但有时候由于编写闭包的方式不同,这一点可能不会那么明显. var

[Swift]Day15:闭包中的循环强引用

闭包中的循环强引用 解决闭包和类实例之间的循环强引用可以通过定义捕获列表来实现. 捕获列表 捕获列表中的每个元素都是由weak或者unowned关键字和实例的引用(如self)成对组成.每一对都在方括号中,通过逗号分开: lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in } 我们需要判断捕获列表中的属性是弱引用还是

swift中闭包的循环引用

首先我们先创造一个循环引用 var nameB:(()->())? override func viewDidLoad() { super.viewDidLoad() let bu = UIButton(type: .ContactAdd) bu.addTarget(self, action: "tap", forControlEvents: .TouchUpInside) view.addSubview(bu) run { print("name") sel

python 闭包中引用的变量值变更问题

python的闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变. 如下: def count():     fs = []     for i in range(1, 4):         def lazy_count(j):             def cou():                 return j*j             return cou         r = lazy_count(i)     

浅谈JS闭包中的循环绑定处理程序

初学者经常碰到的,即获取HTML元素集合,循环给元素添加事件.在事件响应函数中(event handler)获取对应的索引.但每次获取的都是最后一次循环的索引.原因是初学者并未理解JavaScript的闭包特性. 前几天工作中写前端js代码时,遇到了遍历元素给它添加单击事件. (PS:之前也在<jQuery基础教程>第四版中看过讲循环绑定处理程序的内容,当时估计也没怎么用心看,所以没记起来.) 大神要是知道这类情况,可以关掉窗口,写这些主要是给像我一样的小白看的,谢谢! 先贴上错误的例子让大家

关于javascript闭包中的this对象

我们知道, this对象是运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象.<Javascript高级程序设计> 在下面的例子中,理解闭包中的this对象. var name = "The Window"; var object = { name: "My object", getNameFunc: function() { return function() { retur

编写高质量代码改善C#程序的157个建议——建议38:小心闭包中的陷阱

建议38:小心闭包中的陷阱 先看一下下面的代码,设想一下输出的是什么? static void Main(string[] args) { List<Action> lists = new List<Action>(); for (int i = 0; i < 5; i++) { Action t = () => { Console.WriteLine(i.ToString()); }; lists.Add(t); } foreach (Action t in list

C++中的引用

引用就是某个目标变量的别名,对引用的操作与对变量的直接操作效果完全相同. 引用的声明方法:类型标识符 &引用名=目标变量名: 如下:定义引用ra,它是变量a的引用,即别名. int a; int &ra=a; 1)  声明一个引用,必须进行初始化. 2)引用声明完毕后,相当于目标变量有两个名称即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名. 3)引用本身不是一种数据类型,因此引用本身不占存储单元,系统也不会给引用分配存储单元. 4)不能建立数组的引用.因为数组是一个由若干个