【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
目录
相关文章
|
12天前
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
6月前
|
缓存 JavaScript 前端开发
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
该文章详细讲解了JavaScript中的作用域、闭包概念及其应用场景,并简要分析了函数柯里化的使用。
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
|
7月前
|
缓存 前端开发 JavaScript
彻底理解前端闭包
【8月更文挑战第7天】彻底理解前端闭包
54 1
|
7月前
|
存储 JavaScript 前端开发
JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
76 0
|
10月前
|
设计模式 JavaScript 前端开发
js开发:请解释闭包(closure)是什么,以及它的用途。
【4月更文挑战第23天】闭包是JavaScript中的一个重要概念,允许函数访问并操作外部作用域的变量,常用于实现私有变量、模块化和高阶函数。私有变量示例展示了如何创建只在特定函数内可访问的计数器。模块化示例演示了如何封装变量和函数,防止全局污染。最后,高阶函数示例说明了如何使用闭包创建接受或返回函数的函数。
54 0
|
10月前
|
自然语言处理 JavaScript 前端开发
JavaScript基础知识:什么是闭包(Closure)?
JavaScript基础知识:什么是闭包(Closure)?
79 0
|
10月前
|
JavaScript 前端开发 Java
学习Javascript闭包(Closure)
学习Javascript闭包(Closure)
59 0
|
自然语言处理 JavaScript 前端开发
JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型(上)
JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型
|
C语言 C++
【Scheme】编程学习 (四) —— 递归
Scheme 编程通常的使用方法为递归
145 0
|
自然语言处理 C语言 C++
【Scheme】编程学习 (二) —— 基础
Scheme 编程语言学习第二节基础
156 0