• SICP学习笔记(2.2.1)


                                                                                           SICP学习笔记(2.2.1)

                                                                                                 周银辉

    1,两种闭包

    这里所谓的两种闭包(Closure),一个来自于 “数学世界”,一个来自于“计算机世 界”,虽然都叫闭包,但他们几乎指的是完全不同 的两个东西,下面分别来说说。

    • 1.1 数学中的闭包:
        我们假设有一个集合S,以及一个运算P。假设将运 算P应用到S中的元素后产生的新元素仍在集合S中,那么 我们说集合S在运算P下是“闭合的”(Closed )。比如说自然数集N在加法运算下是闭合的,因为自然 数相加得到的结果仍然是自然数。但自然数集在减法下则 不是闭合的了,因为两个自然数相减得到的结果可能超出 自然数范围(比如得到的值为负数)。于是,我们就想办 法将自然数集扩大到集合S'(扩大的意思是,N是 S’的子集),使得 S' 可以在减法下是闭合的,很 简单,我们可以找到整数集合Z以及实数集R等等,在我们 找到的诸多集合中,其中有一个范围“最小” 的集合,使得其刚好在加法下闭合,这个“最小集 ”我们就称为自然数集N在减法下的“闭包 ”。推广到一般情况下而言:如果集合S在运算P是 不闭合,那么我们通常可以找到(也有可能找不到)一个 包含了S的最小集合S' ,使得 S' 在运算P下闭合,那么 我们就将S' 称为集合S在运算P下的闭包。回过头来我们 又可以这样来理解“闭合的”三个字:如果一 个集合S在运算P下的闭包等于集合本身,那么那么我们说 集合S在运算P下是“闭合的”。
      关于 SICP中提到比“闭包性质”,可以 这样来简单理解:假设有一个定义上集合S上的运算P,如 果P的运算结果包含在S中,那么我们说运算P满足“ 闭包性质”。站在数学的角度如果运算P产生了在集 合S之外的元素,那么对于运算 P的定义是不合理的,它 至少应该定义在S的闭包上。比如我们将减法定义在了自 然数上,那么着是不合理的,因为它可能产生自然数之外 的元素,也就是说如果你来发明“减法”的话 ,你至少应该将在定义在整数上。

    • 1.2 计算机中的闭包:
      我们先看一个Javascript函数:
      function F(  )
      {
          
      var a = 5
          
      return function G(b)
          {
              al ert(a
      +b)
          }
      }
        假设我们做如下调用 F()(6) ,其将如何求值呢? 先调用函数F,在函数 F 中我们声明了一个局部变量a, 其值为5,然后函数F将函数G作为返回值进行返回,局部 变量a的生命周期结束,然后作为返回值的函数G将被调用 ,其参数为 6,进入G的函数体后,其将a和6求和并将值 作为参数传递给alert,这时的求和运算似乎有点郁闷了 :我上哪去找变量a啊?其是函数F中的局部变量并且声明 周期早就结束了。
        的确,如果没有闭包的概念,上面的调用的确是不 合法的,但实际调试告诉我们上面的代码能很好地运行, 让我慢慢来解释。
        理想情况之下,我们总希望函数时“纯粹的 ”,一个纯粹的函数具有这样的性质:第一,对于 相同的输入,其总能返回相同的输出;其次,求值结果不 会产生任何副作用。具有这样的性质的函数我们就称之为 “纯函数” (Pure function),纯 函数具有很多优点,比如利于调式,利于并行运算等等。 但在实际编程过程中要做到完全的纯函数很难,一个函数 往往要依赖一些外部变量,也就是所谓的“自由变 量”(参考SICP学习笔记1.1.7~1.1.8),就像上面 的函数G,其依赖自由变量a (对于相同的输入有可能得 不到相同的输出,所以其是“不纯的”),我 们可以将其依赖的自由变量简单地理解成“要正确 调用该函数所必需的环境或上下文”,当这些不纯 的函数在调用的时候,那些自由变量必须正确地得到求值 (或调用),也就是说我们必须为该次函数调用创建其所 必需的环境,如果不能,那么程序就会报错,比如 “引用了未定义的变量”等等。既然上面的 Javascript代码能正确执行,那么就说明 Javascript具 有某种能力来为函数G创作出其所必需的环境,这就是创 建“闭包”的能力。当函数 F 返回时,其发 现函数G依赖于其局部变量a(注意,局部变量四个字很重 要,这说明了a是F的约束变量),其将会把变量a和函数G 打包成一个整体而形成一个所谓的“闭包”进 行返回,由于a和G被打包到了一起,所以当G被调用时其 能够访问到a,也就是说它的计算环境能很好地被创建出 来。
        所以,就一般性而言,计算机编程语言中的“ 闭包” 是指代码块与代码块运行环境所组成的一个 实体。注意,这里的运行环境并不是指其所需要的所有环 境的全集,实际上只是一个其所必需在此打包的那些元素 。比如上面的代码,如何函数G同时还依赖于一个外部变 量c,而c是函数F的自由变量,甚至是一个全局变量,那 么c就不会被包含在这个闭包中,也没有必要包含。
      最后,来句经典的:包含行为的数据叫对象,包含 数据的行为叫闭包。


    2,有关List的一些操作

    关于如何由序列构造出List等等,看看SICP的2.2.1就 很清楚了,这里补充几个List上的一些操作

    • flatten,将列表“展平”
      比如 (flatten '((2 3) 4 5)) 的结果应该为 (2 3 4 5)
      (define (flatten theList)
        (cond
          ((null? theList) '())
          ((pair? (car theList)) (append (flatten (car theList)) (flatten (cdr theList))))
          (else (cons (car theList) (flatten (cdr theList))))))

    • depth,求列表的嵌套深度
      比如 (depth '((2 3) 4 5 (6 (7 8)))) 的结果应 该为 3
      (define (depth theList)
        (if (not (pair? theList))
            0
            (max (+ 1 (depth (car theList)))
                     (depth (cdr theList)))))

    • reverse,将列表翻转
      比如 (reverse '( 1 2 3 4)) 的结果应该为 (4 3 2 1)
      (define (reverse theList)
        (if(null? theList)
            '()
            (append (reverse (cdr theList)) (list (car theList)))))


     3,练习2.17

    “最后一个Pair”也就是说该Pair的cdr为 空,所以:
    (define (lastPair theList)
      (if (null? (cdr theList))
          theList
          (lastPair (cdr theList))))

     比如 (lastPair '(1 2 3 4)) 的结果为 (4) ,   (lastPair '((2 3) 4 5 (6 (7 8))))的结果为 ((6 (7 8)))

    4,练习2.18

    上面已经给出答案了,大致解法是这样的,假设 列 表L: '(a b c d) 其翻转等于将列表L的第一个元素追加 到剩余元素构成的列表的反转列表的后面而成的新列表, 听起来好拗口啊,用数学公式表示就是 R(L) = append ( R(M) first ), 其中R(L)表示列表L的反转, append( a b) 表示将b追加到a后面构成新列表,first表示列表L的 第一个元素,M 表示列表除去first后由剩余元素构成的 新列表,所以写成Scheme代码就如下:
    (define (reverse theList)
      (if(null? theList)
          '()
          (append (reverse (cdr theList)) (list (car theList)))))

    5,练习2.19

    first-denomination, 第一种面额,即求出列表的第 一个元素,car
    except-first-denomination, 除去第一种面额后构成的 新列表,cdr
    no-more? 列表中没有其他面额了,即空列表  '()

    6,练习2.20

    呵呵呵,比较有意思的一个题目,要很好地完成这个 题目需要先理解一下下面这个简单的“累加运算 ”的例子:
    假设我们从一个数开始练习累加到另外一个数,比如从1 累加到5(结果为15),我们可以编写一个如下的迭代算 法
    (define (add-iter from to result)
      (if (> from to)
          result
          (add-iter (+ from 1) to (+ result from))))

    (define (add from to)
      (add-iter from to 0))

    注意到上面的add-iter,为了“累计”最 终结果,我们设置了一个名为result的累加器,用来保存 累加值。当进入下一个迭代时我们将“计步器 ”from加1,并将需要被累计的值(这里被累计的值 和计步器的值刚好相同)累加到result上。 当迭代结束 时我们返回result。
    OK,现在将上面的题稍稍改一下,仅仅累计偶数(比如从 1累加到5,结果应该是6):
    (define (add-even-iter from to result)
      (if (> from to)
          result
          (add-even-iter (+ from 1) to
            (if (even? from)
                 (+ result from)
                 result))))

    (define (add-even from to)
      (add-even-iter from to 0))

    我们发现后一个版本与前一个唯一不同的地方是在累 加是判断了即将被累计的值是否为偶数,是则累加。
    将其分解一下, 条件:偶数; 初始值:0;满足条 件时的操作:算术加

    现在回过头来看我们目前的题目,我们要返回与列表 第一元素奇偶性相同的子列表。
    同样,我们将问题分解一下, 条件:与第一个元素的奇 偶性相同;初始值:空列表 nil;满足条件时的操作:列 表追加 append

    然后,依葫芦画瓢吧:

    (define (F a b)
      (equal? (even? a) (even? b)))

    (define (same-parity-iter firstOne others result) ;注意这里没有小数点
      (if (= (length others) 1)
           (if (F firstOne (car others))
                (append result (list (car others)))
                result)
          (same-parity-iter firstOne (cdr others)
                                       (if(F firstOne (car others))
                                            (append result (list(car others)))
                                            result))))

    (define (same-parity firstOne . others)  ; 注意这里有小数点
      (same-parity-iter firstOne others (list firstOne)))

    其中 F 是判断两个数奇偶性是否相同的函数

    注意same-parity-iter中有段代码是重复的,我们可 以将它重构出来,假设叫G,最后完整的代码如下:
    (define (F a b)
      (equal? (even? a) (even? b)))

    (define (G firstOne others result)
      (if(F firstOne (car others))
         (append result (list(car others)))
         result))

    (define (same-parity-iter firstOne others result)
      (if (= (length others) 1)
          (G firstOne others result)
          (same-parity-iter firstOne (cdr others)
                             (G firstOne others result))))     

    (define (same-parity firstOne . others)
      (same-parity-iter firstOne others (list firstOne)))

    ;test
    (same-parity 1 2 3 4 5 6 7 8 9)


     7,练习2.21

    比较简单,直接贴答案了:
    (define (square a) (* a a))
    (define nil '())

    (define (square-list items)
      (if (null? items)
          nil
          (cons (square (car items)) (square-list (cdr items)))))

    (define (square-list2 items)
      (map square items))

    (square-list (list 1 2 3 4))
    (square-list2 (list 1 2 3 4))

    8,练习2.22

    我一般会这么办:

    (define (square a) (* a a))
    (define nil '())

    (define (square-list items)
      (define (iter things answer)
        (if (null? things)
            answer
            (iter (cdr things)
                    (append answer
                                  (list (square (car things)))))))
    (iter items nil))

    (square-list (list 1 2 3 4))

    9,练习2.23

    (foreach proc theList)  依次对列表theList中的 元素应用过程proc,也就是说先对列表的第一个元素应用 proc,然后再对除去第一个元素后剩余元素构成的子列表 subList 调用(foreach proc subList),另外,需要判断 是否存在第一个元素(列表长度是否大于0),是否存在 子列表(列表长度是否大于1):

    (define (foreach proc theList)
      (if (> (length theList) 0)
          (begin (proc (car theList))
                      (if(> (length theList) 1)
                           (foreach proc (cdr theList))))))
    ; test  
    (foreach (lambda (x) (newline) (display x)) (list 57 321 88)) 


     注:这是一篇读书笔记,所以其中的内容仅 属个人理解而不代表SICP的观点,并随着理解的深入其中 的内容可能会被修改

  • 相关阅读:
    ds
    使用js做的贪吃蛇游戏的知识总结
    使用js实现的关联表单
    使用jquery实现的计算器功能
    vue学习(2)关于过渡状态
    vue学习(1)生命周期
    vue学习(0)写在前面的一句话
    bootstrap(7)关于进度条,列表,面板
    bootstrap(6)分页,翻页,徽章效果
    异常捕获
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1595500.html
Copyright © 2020-2023  润新知