封装和闭包有什么区别?
What is the difference between encapsulation and closure?
关于封装和闭包,我确实有些不明白。我相信封装是不能改变的,除非它被代码改变。但是当我被要求解释如何在代码上应用闭包和封装时,我真的无法理解。
例如:
(define new-cercle #f)
(let ((n 0))
(set! new-cercle
(lambda (rayon)
(begin
(set! n (+ n 1))
(lambda (msg)
(cond ((eq? msg ’circonference)
(* 2 3.14 rayon))
((eq? msg ’surface)
(* 3.14 rayon rayon))
((eq? msg ’nb-cercles)
n)))))))
n
是封装的吧?所以问题是:解释一下封装和闭包是如何应用到这段代码上的。
我不明白的另一件事是为什么 let
必须在 lambda 之上?为什么我把它放在lambda
下面,函数就不能正常工作,没有累加器?
(define acc
(let ((n 1))
(lambda (x)
(set! n (* n x))
n)))
我希望有人能用简单的方式向我解释这一点,因为当我 google 解释它时,老实说,我对大多数主题的复杂示例一无所知。
封装模式的名称,涉及将一些相关项放在一个容器中,然后随该容器一起移动,并通过该容器上的某种访问机制进行引用的任何情况。这些项目可以是 运行 时间值,或编译时标识符或其他任何东西。由多个字段组成的对象封装了字段:一个cons
单元格封装了car
和cdr
。一个 class 封装了插槽。在一些对象系统中,方法也是。编译单元封装了它们的全局定义,例如函数和变量。
OOP中流行的"encapsulate"指的是定义一个class作为一个单元,其中包含数据的定义,以及对其进行操作的方法:代码和数据是一体的"capsule"。 (Common Lisp 对象系统不是这样的:方法没有封装在 classes 中。)
闭包是另外一种东西,非常具体:它是程序代码的主体,连同它的词法环境,具体化到一个函数类型的对象中。闭包的主体在被调用时对两组名称具有可见性:闭包的函数参数,以及创建闭包的词法范围内的周围名称。闭包是封装的一个例子:它将代码体与词法范围封装在一起。访问胶囊的唯一方法是通过函数:函数就像一个"method",捕获的词法环境的元素就像一个对象中的"slots"。
(通过组合代码和数据,Lisp 闭包比 Lisp class 对象更类似于流行的封装概念。)
关于那个有趣的词:在计算机科学中,"reify" 程序的某些方面是获取不是第一个 class 对象的东西,并以某种方式将其变成一个。
几乎任何适用于理解程序的可识别概念都可能被具体化。 (聪明的人只需要提出一个明智的建议即可。)
例如,在给定执行点的整个未来计算可以具体化,结果对象称为延续 (更准确地说,是无限延续)。
当延续运算符捕获未来的计算时,该未来就变成了假设:它实际上并没有发生(不执行)。相反,一个替代的 future 执行,其中延续返回给操作员的调用者,或传递给调用者指定的函数。现在掌握了这种延续的代码可以使用它来显式调用原始的、捕获的未来,就好像它是一个函数一样。或者选择不这样做。换句话说,程序控制流(执行这个块或者不执行这个,或者执行几次)变成了一个函数对象(调用这个函数或者不调用它,或者调用多次)。
对象是具体化的另一个例子:模块的具体化。老式程序分为具有全局函数和全局变量的模块。这个 "module" 结构是一个我们可以在程序中识别并有用地应用于描述此类程序的概念。它很容易具体化:我们可以想象,如果我们有一个 运行 时间的对象,它是 "module",具有所有相同的属性:即包含函数和数据?并且,转眼间:基于对象的编程诞生了,具有同一个模块的多个实例化等优点,因为变量不再是全局的。
关于cercle
和rayon
:
首先,new-cercle
的行为类似于对象的构造函数:它是一个可以从任何地方调用的全局函数。它维护已构造了多少个对象的计数。只有那个函数可以访问计数器,所以它被封装了。 (实际上不仅那个函数可以访问它,代表圆实例的闭包也可以访问它!)这是一个类模块封装的例子。它模拟模块,如语言 Modula-2 和类似语言,例如在文件范围内具有 static
个变量的 C 语言翻译单元。
当我们调用 new-cercle
时,我们必须为 rayon
参数提供一个参数。生成并返回一个对象。该对象恰好是作为词法闭包生成的函数。这个闭包捕获了 rayon
参数,从而封装了这个值:对象知道它自己的半径。我们可以重复调用new-cercle
,得到不同的圆圈实例,每个圆圈都有自己的rayon
。这个rayon
是对外不可见的;它被打包在闭包中,并且只对那个函数可见。
我们通过容器上的 "message" API 间接访问人造丝容器。我们可以用消息符号 surface
调用该函数,它通过返回表面积来回复。当前可用消息的 None 直接显示 rayon
,但我们可以为此提供访问器消息,甚至是更改半径的消息。甚至还有一条消息访问共享变量 n
,圆的计数,它的行为类似于对象系统(静态槽)中的 class 变量:圆的任何实例都可以报告有多少个圆已经建成。 (请注意,此计数不会告诉我们当前存在多少圈:当圈变成垃圾并被回收时它不会减少:没有 finalization)。
无论如何,我们显然有一个容器,其内容只能通过接口访问。该容器将代码和数据绑定在一起,因此它不仅是封装,而且可以说是流行的 OOP 意义上的封装。
您可能遇到了困难,因为在微不足道的情况下,一些差异消失了。例如 (let ((n 1)) (lambda (x) n))
和 (lambda (x) (let ((n 1)) n)
都给你基本相同的功能。
在你的例子中
(define acc (let ((n 1))
(lambda (x) (set! n (* n x)) n)))
let
和 lambda
的顺序很重要。如果将它们交换为 (lambda (x) (let ((n 1)) ...
,则每次调用此函数时,n
将再次绑定到 1
。相反,您希望有一些位置 n
以值 1
开头,并且可以由您的函数修改,并且在您的函数完成后不会消失,这就是您拥有 (let ((n 1)) (lambda (x) (set! n ...
.
由内部 lambda
构造的函数捕获外部 n
的使用,并在其自身存在期间一直保持其位置。它还封装了 n
因为除了这个函数之外没有别的东西可以引用它。我们还说该函数被 n
的周围绑定关闭,并且该函数是一个闭包(n
)。
阅读有关词法作用域的内容也可能对您有所帮助。
关于封装和闭包,我确实有些不明白。我相信封装是不能改变的,除非它被代码改变。但是当我被要求解释如何在代码上应用闭包和封装时,我真的无法理解。
例如:
(define new-cercle #f)
(let ((n 0))
(set! new-cercle
(lambda (rayon)
(begin
(set! n (+ n 1))
(lambda (msg)
(cond ((eq? msg ’circonference)
(* 2 3.14 rayon))
((eq? msg ’surface)
(* 3.14 rayon rayon))
((eq? msg ’nb-cercles)
n)))))))
n
是封装的吧?所以问题是:解释一下封装和闭包是如何应用到这段代码上的。
我不明白的另一件事是为什么 let
必须在 lambda 之上?为什么我把它放在lambda
下面,函数就不能正常工作,没有累加器?
(define acc
(let ((n 1))
(lambda (x)
(set! n (* n x))
n)))
我希望有人能用简单的方式向我解释这一点,因为当我 google 解释它时,老实说,我对大多数主题的复杂示例一无所知。
封装模式的名称,涉及将一些相关项放在一个容器中,然后随该容器一起移动,并通过该容器上的某种访问机制进行引用的任何情况。这些项目可以是 运行 时间值,或编译时标识符或其他任何东西。由多个字段组成的对象封装了字段:一个cons
单元格封装了car
和cdr
。一个 class 封装了插槽。在一些对象系统中,方法也是。编译单元封装了它们的全局定义,例如函数和变量。
OOP中流行的"encapsulate"指的是定义一个class作为一个单元,其中包含数据的定义,以及对其进行操作的方法:代码和数据是一体的"capsule"。 (Common Lisp 对象系统不是这样的:方法没有封装在 classes 中。)
闭包是另外一种东西,非常具体:它是程序代码的主体,连同它的词法环境,具体化到一个函数类型的对象中。闭包的主体在被调用时对两组名称具有可见性:闭包的函数参数,以及创建闭包的词法范围内的周围名称。闭包是封装的一个例子:它将代码体与词法范围封装在一起。访问胶囊的唯一方法是通过函数:函数就像一个"method",捕获的词法环境的元素就像一个对象中的"slots"。
(通过组合代码和数据,Lisp 闭包比 Lisp class 对象更类似于流行的封装概念。)
关于那个有趣的词:在计算机科学中,"reify" 程序的某些方面是获取不是第一个 class 对象的东西,并以某种方式将其变成一个。
几乎任何适用于理解程序的可识别概念都可能被具体化。 (聪明的人只需要提出一个明智的建议即可。)
例如,在给定执行点的整个未来计算可以具体化,结果对象称为延续 (更准确地说,是无限延续)。
当延续运算符捕获未来的计算时,该未来就变成了假设:它实际上并没有发生(不执行)。相反,一个替代的 future 执行,其中延续返回给操作员的调用者,或传递给调用者指定的函数。现在掌握了这种延续的代码可以使用它来显式调用原始的、捕获的未来,就好像它是一个函数一样。或者选择不这样做。换句话说,程序控制流(执行这个块或者不执行这个,或者执行几次)变成了一个函数对象(调用这个函数或者不调用它,或者调用多次)。
对象是具体化的另一个例子:模块的具体化。老式程序分为具有全局函数和全局变量的模块。这个 "module" 结构是一个我们可以在程序中识别并有用地应用于描述此类程序的概念。它很容易具体化:我们可以想象,如果我们有一个 运行 时间的对象,它是 "module",具有所有相同的属性:即包含函数和数据?并且,转眼间:基于对象的编程诞生了,具有同一个模块的多个实例化等优点,因为变量不再是全局的。
关于cercle
和rayon
:
首先,new-cercle
的行为类似于对象的构造函数:它是一个可以从任何地方调用的全局函数。它维护已构造了多少个对象的计数。只有那个函数可以访问计数器,所以它被封装了。 (实际上不仅那个函数可以访问它,代表圆实例的闭包也可以访问它!)这是一个类模块封装的例子。它模拟模块,如语言 Modula-2 和类似语言,例如在文件范围内具有 static
个变量的 C 语言翻译单元。
当我们调用 new-cercle
时,我们必须为 rayon
参数提供一个参数。生成并返回一个对象。该对象恰好是作为词法闭包生成的函数。这个闭包捕获了 rayon
参数,从而封装了这个值:对象知道它自己的半径。我们可以重复调用new-cercle
,得到不同的圆圈实例,每个圆圈都有自己的rayon
。这个rayon
是对外不可见的;它被打包在闭包中,并且只对那个函数可见。
我们通过容器上的 "message" API 间接访问人造丝容器。我们可以用消息符号 surface
调用该函数,它通过返回表面积来回复。当前可用消息的 None 直接显示 rayon
,但我们可以为此提供访问器消息,甚至是更改半径的消息。甚至还有一条消息访问共享变量 n
,圆的计数,它的行为类似于对象系统(静态槽)中的 class 变量:圆的任何实例都可以报告有多少个圆已经建成。 (请注意,此计数不会告诉我们当前存在多少圈:当圈变成垃圾并被回收时它不会减少:没有 finalization)。
无论如何,我们显然有一个容器,其内容只能通过接口访问。该容器将代码和数据绑定在一起,因此它不仅是封装,而且可以说是流行的 OOP 意义上的封装。
您可能遇到了困难,因为在微不足道的情况下,一些差异消失了。例如 (let ((n 1)) (lambda (x) n))
和 (lambda (x) (let ((n 1)) n)
都给你基本相同的功能。
在你的例子中
(define acc (let ((n 1))
(lambda (x) (set! n (* n x)) n)))
let
和 lambda
的顺序很重要。如果将它们交换为 (lambda (x) (let ((n 1)) ...
,则每次调用此函数时,n
将再次绑定到 1
。相反,您希望有一些位置 n
以值 1
开头,并且可以由您的函数修改,并且在您的函数完成后不会消失,这就是您拥有 (let ((n 1)) (lambda (x) (set! n ...
.
由内部 lambda
构造的函数捕获外部 n
的使用,并在其自身存在期间一直保持其位置。它还封装了 n
因为除了这个函数之外没有别的东西可以引用它。我们还说该函数被 n
的周围绑定关闭,并且该函数是一个闭包(n
)。
阅读有关词法作用域的内容也可能对您有所帮助。