美文网首页
1.1 The Elements of Programming

1.1 The Elements of Programming

作者: Cloneable | 来源:发表于2022-07-26 15:19 被阅读0次

编程语言应该具备将简单的概念组合为复杂概念的能力,这个能力与三种机制相关

  • 基础表达式
  • 组合
  • 抽象

组合基本元素

+-*/ 及数字等预先定义在解释器中的都是基本元素,在 Lisp 中通过小括号的方式将这些基本元素组合起来,例如:

(+ 1 10)
(/ 6 3)

分别由位于最左侧的操作符与其他元素,也就是操作数组成,其中操作数会被解释器解释为计算机机器指令,再将操作数应用于此机器指令。

Lisp 表达式通过前缀表达式(也就是将操作符置于表达式最左侧)的方式表述,这有利于参数的数量灵活增减,同样也利于嵌套表达式的编写,例如:

(+ (- 10 1) (* 2 4))

表达式的特殊形式

除了上述常见的表达式之外,还存在情况比较特殊的表达式,如 definecondif 等,这些表达式并不是按照统一的表达式规则运算,而是各有规则。

define

define 有主要有两种使用场景,绑定参数和定义程式,例如:

(define size 3)

是将 3 与 名为 size 变量或对象绑定,在同一环境中使用 size 时输出的结果就是 3,而定义程式的情况如下:

(define (<name> <formal parameters>) <body>)

(define (square x)
  (* x x))

通过 define 可以将一个名字与一个基本元素或一段表达式关联,这也是 Lisp 这门编程语言抽象能力的体现。

条件表达式

cond 表达式的结构如下:

(cond (<p1> <e1>)
      (<p2> <e2>)
      (<p3> <e3>))

(define (abs x)
  (cond ((> x 0) x)
        ((= x 0) 0)
        ((< x 0) (- x))))

p 表示逻辑判断的表达式,结果为 true 时会执行对应的 e,也就是结果表达式,并将计算结果返回,如果 p 的结果为 false,则按此规则执行下一条用例。如果用例只有两条时也可如下编写:

(cond (<p1> <e1>)
      (else <e2>))

(define (abs x)
  (cond ((< x 0) (- x))
        (else x)))

虽然 cond 不是表达式而是特殊形式,但 else 是表达式,因为 else 会应用所有操作数。上述结构也可以用 if 特殊形式改写:

(if <predicate> <consequent> <alternative>)

(define (abs x)
  (if (< x 0)
      (- x)
      x))

除此之外还可以使用 andornot 这些复合条件表达式,如下:

(and <e1> <e2> ... <en>)
(or <e1> <e2> ... <en>)
(not <e>)

这几种条件表达式除了 not 外都是特殊形式,andor 都符合开关原则,不过 and 所有表达式结果都为 true 时最终返回最后一个表达式的运算结果,而 or 在所有表达式都为 false 时最终只返回 false。

替换模型

模型的目的是帮助我们思考程式是如何被运算的,而不是在描述解释器如何运作,随着对解释器的深入了解模型也会遇到问题,此时就需要寻找更加合适的模型帮助我们思考。

替换模型会将程式的形参替换为对应的数值,并计算表达式结果。模型也存在不同的运算方式,分别是 normal-order evaluationapplicative-evaluation

applicative-evaluation

有如下程式:

(define (sum-of-squares x y)
  (+ (square x) (square y)))

(define (square x) (* x x))

(define (f x)
  (sum-of-squares (+ x 1) (* x 2)))

通过 applicative-evaluation 的方式展开表达式 (f 5)

(sum-of-squares (+ 5 1) (* 5 2))
(sum-of-squares 6 10)
(+ (square 6) (square 10))
(+ (* 6 6) (* 10 10))
(+ 36 100)
136

applicative-evaluation 会将操作数计算得到结果再应用。

normal-order evaluation

继续使用 applicative-order evaluation 的程式作为例子,通过 normal-order evaluation 的方式展开表达式 (f 5)

(sum-of-squares (+ 5 1) (* 5 2))
(+ (square (+ 5 1)) (square (* 5 2)))
(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2)))

(+ (* 6 6) (* 10 10))
(+ 36 100)
136

normal-order evaluation 会将表达式完全展开后再应用,你会发现有些表达式在收敛时被重复计算,例如 (+ 5 1)。这也是解释器使用 applicative-order evaluation 的原因之一,可以在计算时提高效率,另一个原因是当后期出现无法使用替换模型无法解释的情况时,使用 normal-order evaluation 会遇到更多问题。

程式的黑盒抽象

当一个程式被使用时它应该如同黑盒一般,使用者不需要关注其中的实现细节,如同 square 程式。所以从黑盒抽象的层次来说会忽略程式的实现细节,而形参名称也会被忽略(是指不同的开通名称对程式的运算结果无影响)。

并且形参属于绑定变量声明,它的作用域只有对应的程式体内,只要程式内部对形参统一修改名称对程式的结果并无影响。

不过关于黑盒的抽象还有个问题需要解决,如果像下述例子一样定义程式,其他的程式想定义一个自己的 sum-of-squares ,而不影响程式 f 的结果便无法实现,使用者不得不了解其中实现的细节。

(define (sum-of-squares x y)
  (+ (square x) (square y)))

(define (square x) (* x x))

(define (f x)
  (sum-of-squares (+ x 1) (* x 2)))

不过我们可以通过代码块结构进入程式的内部定义,如下:

(define (f x)
  (define (sum-of-squares x y)
    (+ (square x) (square y)))
  (define (square x)
    (* x x))
  (sum-of-squares (+ x 1) (* x 2)))

这种方式既有效地将大型程序切割为易维护的代码片段,又解决了包命名的问题。

相关文章

网友评论

      本文标题:1.1 The Elements of Programming

      本文链接:https://www.haomeiwen.com/subject/zdezirtx.html