【Scheme】编程学习 (三) —— 闭包

简介: 本节主要讲述 Scheme 中闭包概念及使用

原视频地址
https://www.bilibili.com/video/BV1Kt411R7Wf?p=3

本节主要内容关于 Closures (闭包)

  • Functions in functions 在函数中使用函数
  • Using outer symbols 使用外部的符号
  • Returning functions 如何返回一个函数
  • Closures 什么是闭包
  • Uses 闭包的使用
  • Discussion 讨论

一、Functions in functions

(define (sum-of-squares x y)
    (define (square a)
        (* a a))
    (define (sum b c)
        (+ b c))
    (sum (square x) (square y))) ; 函数实际的函数体

函数内定义函数,有些类似 C++ 函数体内的函数对象, lambda 函数。

double SumOfSquare(double x, double y)
{
   
    auto square = [](double a)-> double
    {
    return a * a; };

    auto sum = [](double b, double c)->double
    {
    return b + c; };

    return sum(square(x), square(y));
}

二、Using outer symbols

使用外部符号

(define (assert-equal a b)

    (define (print-error)
        (display a)
        (display " is not equal to ")
        (display b)
        (newline))

    (if (not (equal? a b)) (print-error) null))

(assert-equal 3 (+ 1 2))
; does nothing
(assert-equal 3 (+ 2 2))
; print 3 is not equal to 4

display 函数用于打印到控制台 (print to console)

内部的函数 print-error 可以访问变量 a 和 b ,有些类似 lambda 函数对象的父域内容捕获

equal? 用于比较 a 和 b 是否相等 (evaluating if a is equal to b)

示例 C++ 代码:

// 函数对象名称 = [捕获](入参) {};
void AssertEqual(int a, int b)
{
   
    // 以值的方式捕获父域中的 a, b
    auto print_error = [a,b]()
    {
   
        std::cout << a << " is not equal to " << b << std::endl;
    };

    if (!(a == b))
    {
   
        print_error();
    }
}

在 scheme 函数中定义函数,定义的函数可以访问外部函数中的变量。 C++ 中需要手动指定。

来看第二个例子

(define (circle-details r)
    (define pi 3.14)
    (define (area) (round (* pi r r)))
    (define (circum) (round (* 2 pi r)))
    (list (area) (circum)))

(circle-details 3)
; (28.0 19.0)

area 面积为 pi * r 的平方
round 表示获取近似值,四舍五入
如果传入圆半径 (radius) 会计算并返回圆的面积和圆的周长。

示例 C++ 代码

#include <cmath> // for round function
std::vector<double> CircleDetails(double r)
{
   
    const double pi = 3.14;
    auto area = [pi,r]() -> double 
    {
    return round(pi * r * r); }
    auto circum = [pi,r]() -> double
    {
    return round(2 * pi * r); }

    return std::vector<double> {
    area(), circum() };
}

表示,scheme 函数内的函数也可以使用外层函数内部定义的符号

三、Returning functions

函数可以返回内部定义的函数

(define (make-add-one)
    (define (inc x) (+ 1 x); 定义过程 (inc x)
    inc); 返回 inc 过程

定义一个过程 make-add-one 不需要任何入参,在过程中定义一个函数,并且返回
inc 过程为返回入参+1的数值。

this is a function which returns a function,这是一个返回值为函数的函数

调用函数 make-add-one

> (make-add-one) 
; # <procedure:inc>

表示,返回一个 function 为 inc。

定义一个符号 myfn 为 make-add-one

(define myfn make-add-one) ; 定义一个符号为 make-add-one
>(myfn 2)
; procedure make-add-one: expects no arguments, give 1

make-add-one doesn't take any argument
调用报错 make-add-one 过程期待无参数,但是给予了一个参数

正确的定义方法为,定义符号 myfn 为调用 make-add-one 的结果

(define myfn (make-add-one))

> (myfn 2)
; 此时调用结果为 3

四、Closures

为了理解闭包的概念,需要理解以下过程

(define (make-add-x x)
    (define (add-x y) (+ x y))
    add-x); 返回 add-x 过程

定义符号 add-3 为调用过程 (make-add-x 3)的结果

(define add-3 (make-add-x 3))
> add-3
;<procedure:add-x>
> (add-3 4)
; call add-3 with argument 4, the answer is 7 
; 调用 add-3 给与实参 4, 结果为 7

这里发生了什么,调用 make-add-x 使用的实参 3, 出于某种原因,被封锁在了 add-3 内部,
the name "closure" means that is not legal to do this kind of thing we just talk about,
closure is a concept that a system is closed in the sense that nothing was allowed to do in it.

“闭包”这个名字意味着做我们刚刚谈论的这种事情是不合法的,
闭包是一个概念,即一个系统是封闭的,从某种意义上说,它不允许在其中做任何事情。

you pass un argument 3, and you use it later. and it's still there
传入一个参数,之后使用,它仍在那里

五、Uses

5.1 Uses - Holding state

使用 - 保持状态

(define (make-counter)
    (define value 0); 定义一个 value, 设置为 0
    (define (counter); 定义一个无参函数 counter 
        (set! value (+ value 1))
        value)
    counter)

set! 是一个函数用于将 value 设置为 value + 1
定义 counter 函数,改变 value 的值,并返回 value。
外部的过程用于返回 counter 过程。

(define mycounter1 (make-counter))

> (mycounter1); it set value+1 and return value of 'value'
; 调用会设置 value 为 value + 1 并返回 value 的值
; 1
> (mycounter1)
; 2
> (mycounter1)
; 3

(define mycounter2 (make-counter))
> (mycounter2)
; 1
> (mycounter1)
; 4 ;结果是独立的
> (mycounter2)
; 2
> (mycounter1)
; 5

调用一次产生一个闭包,

5.2 Uses - Testing

测试

(define (shout display-fn txt)
    (display-fn
        (list->string
            (map
                char-upase
                (string->list txt)))))

定义一个函数为 shout ,传入 display-fn 过程,和 参数 txt。
将输入的字符串,一个字符一个字符设置为大写。并将其作为参数给与 display-fn 过程。

传入正常的 display 和 "boo"

> (shout display "boo")
; BOO
(define (test-shout-displays-upper-case)
    (define displayed "") ; display symbol make it empty string
    (define (fake-display txt)
        (set! displayed txt)); set symbol displayed to value txt

    (shout fake-display "Hello Andy")
    (assert-equal displayed "HELLO ANDY"))

定义一个函数为 test-shout-display-upper-case ,在内部定义一个符号 displayed 为空字符串,用于存储需要显示的字符,然后定义一个函数为 fake-display,入参为 txt,将符号 displayed 内容设置为 txt
assert-equal 用于检测两个字符串是否相同

5.3 Uses - Classes

使用,用于模拟 类,

(define (make-balance)
    (define value 0)
    (define (bal method)
        (define (add-method x)
            (set! value (+ value x)))
        (define (get-method) value)
        (if (equal? method "add")
            add-method
            get-method))
        bal)

make-balance 可以视为 类的构造函数

class MakeBalance
{
   
    int value;

    int AddMethod(int x) {
    value = value + x; }
    int GetMethod() {
    return value; }    
public:
    /*...*/ bal(std::string method)
    {
   
        if (method == "add")
            return &AddMethod;
        else
            return &GetMethod;    
    }
};
(define a (make-balance))

> (a "get")
;#<procedure:get-method> ; 只能返回一个过程 procedure
> ((a "get")) ; 需要结果只能使用调用过程
;0
> ((a "add") 3)
;  nothing happened
> ((a "get"))
; 3

重新定义一个 b

> (define b (make-balance))
> ((b "get"))
; 0
> ((b "add") -1)

> ((b "get"))
;-1
> ((a "get"))
; 3 仍然是 3
目录
相关文章
|
4月前
|
JavaScript 前端开发
解释JavaScript闭包的工作原理,并举例说明其在游戏开发中的应用。
JavaScript闭包允许内部函数访问并保持对外部函数变量的引用,即使外部函数执行结束。当函数返回内部函数时,形成闭包,继承父函数作用域链。在游戏开发中,闭包用于创建具有独立状态和行为的角色实例。例如,`createCharacter`函数创建角色并返回包含属性和方法的对象,内部函数如`getHealth`、`setHealth`和`attack`通过闭包访问并操作角色的变量。这种方式确保了每个角色的状态在不同实例间独立,是实现游戏逻辑的强大工具。
31 2
|
1月前
|
存储 JavaScript 前端开发
JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
28 0
|
3月前
|
Go
Go语言进阶篇——浅谈函数中的闭包
Go语言进阶篇——浅谈函数中的闭包
|
4月前
|
设计模式 JavaScript 前端开发
js开发:请解释闭包(closure)是什么,以及它的用途。
【4月更文挑战第23天】闭包是JavaScript中的一个重要概念,允许函数访问并操作外部作用域的变量,常用于实现私有变量、模块化和高阶函数。私有变量示例展示了如何创建只在特定函数内可访问的计数器。模块化示例演示了如何封装变量和函数,防止全局污染。最后,高阶函数示例说明了如何使用闭包创建接受或返回函数的函数。
30 0
|
4月前
|
存储 Swift
Swift 语言:什么是闭包(Closure)?它们与函数的区别是什么?
Swift 语言:什么是闭包(Closure)?它们与函数的区别是什么?
97 1
|
4月前
|
自然语言处理 JavaScript 前端开发
JavaScript开发基础问题:如何理解闭包及其作用?
JavaScript开发基础问题:如何理解闭包及其作用?
54 5
|
4月前
|
Python
Python学习 -- 高阶、闭包、回调、偏函数与装饰器探究
Python学习 -- 高阶、闭包、回调、偏函数与装饰器探究
33 0
|
11月前
|
Go
理解Go语言变量作用域 示例学习不迷茫!
理解Go语言变量作用域 示例学习不迷茫!
55 0
|
自然语言处理 C语言 C++
【Scheme】编程学习 (二) —— 基础
Scheme 编程语言学习第二节基础
97 0
|
C语言 C++
【Scheme】编程学习 (四) —— 递归
Scheme 编程通常的使用方法为递归
85 0