《如何设计程序》为什么在答案中选择这种方式?

4

我正在使用著名的书籍《程序设计方法》。更具体地说,是第一版(我有实体书)。

在第6章中,有一些关于结构的练习题。其中一道题目需要模拟交通信号灯,并使用效果(变异)来改变它们。

我指的是关于函数nextExercise 6.2.5 练习,该函数应该给出下一个交通灯的颜色。

该书提供的答案是:

(start 50 160)
(draw-solid-disk (make-posn 25 30) 20 'red)
(draw-circle (make-posn 25 80) 20 'yellow)
(draw-circle (make-posn 25 130) 20 'green)

; -------------------------------------------------------------------------

;; clear-bulb : symbol -> true
;; to clear one of the traffic bulbs
(define (clear-bulb color)
  (cond
    [(symbol=? color 'red) 
     (and (clear-solid-disk (make-posn 25 30) 20)
          (draw-circle (make-posn 25 30) 20 'red))]
    [(symbol=? color 'yellow) 
     (and (clear-solid-disk (make-posn 25 80) 20)
          (draw-circle (make-posn 25 80) 20 'yellow))]
    [(symbol=? color 'green)
     (and (clear-solid-disk (make-posn 25 130) 20)
          (draw-circle (make-posn 25 130) 20 'green))]))

;; tests
(clear-bulb 'red)

; -------------------------------------------------------------------------

;; draw-bulb : symbol -> true
;; to draw a bulb on the traffic light
(define (draw-bulb color)
  (cond
    [(symbol=? color 'red) 
     (draw-solid-disk (make-posn 25 30) 20 'red)]
    [(symbol=? color 'yellow) 
     (draw-solid-disk (make-posn 25 80) 20 'yellow)]
    [(symbol=? color 'green)
     (draw-solid-disk (make-posn 25 130) 20 'green)]))

;; tests
(draw-bulb 'green)

; -------------------------------------------------------------------------

;; switch : symbol symbol -> true
;; to switch the traffic light from one color to the next
(define (switch from to)
  (and (clear-bulb from)
       (draw-bulb to)))

;; tests
(switch 'green 'yellow)
(switch 'yellow 'red)

; -------------------------------------------------------------------------

;; next : symbol -> symbol
;; to switch a traffic light's current color and to return the next one  
(define (next current-color) 
  (cond 
    [(and (symbol=? current-color 'red) (switch 'red 'green)) 
     'green] 
    [(and (symbol=? current-color 'yellow) (switch 'yellow 'red)) 
     'red] 
    [(and (symbol=? current-color 'green) (switch 'green 'yellow)) 
     'yellow]))

(next 'red)
(next 'green)
(next 'yellow)
(next 'red)

在下一个函数中,我做了类似的事情,并在提供的测试中实现了相同的结果。
(define (next current-color) 
  (cond 
    [(symbol=? current-color 'red) (switch 'red 'green)] 
    [(symbol=? current-color 'yellow) (switch 'yellow 'red)] 
    [(symbol=? current-color 'green) (switch 'green 'yellow)]))

与书中的答案不同,我的代码没有使用and,也没有让一个松散的单个符号(例如'red)。

这种差异引起了我的兴趣,因为这本书在强调如何设计代码方面非常重要。令我感到困惑的是,原始解决方案使用了and(组合连续效果),似乎除了在每个条件语句结尾使用“孤独”的'red,'yellow或'green之外,没有必要。
我不明白最后一个符号语句或and的目的。
是否有一些风格上或概念上的原因,采用这种看起来更冗长、不够清晰的方法?
我正在阅读这本书,以改善我编写代码的方式。

1
这种使用 and 来序列化副作用的方式是一个缺陷,第二版通过改用更加函数式和灵活的 "universe" teachpack 来修复了这个问题。这是一个更加清晰的设计,我建议至少查看第二版,看看它是如何实现的。 - Ryan Culpepper
1个回答

5
作为Scheme语言的一种,Racket是一种面向表达式的语言。这意味着复合表达式中的最后一个表达式是整个表达式的值,其中包括引用的符号。它的值,即该符号本身,将成为返回值。调用函数(next current-color)会切换交通灯的颜色并返回指示交通灯新颜色的符号。
;; next : symbol -> symbol

您的代码切换颜色并返回true(根据switch规范):
;; switch : symbol symbol -> true
;; your-next : symbol -> true

这改变了函数next的使用方式。有了这本书的设计,我们可以这样写:
....
   (let loop ( ... )
       .....
       (let ((current-color (next current-color)))
           ......
           ))
....

使用您的设计,这种自然循环代码的风格是不可能的。
一般而言,这些规范被称为类型,我们在代码中使用函数时会让类型指导我们。它们帮助我们看清输入和输出的内容,这样我们就可以“连接匹配的电线”,所谓。

太好了。当我提问时,这对我来说是看不见的。这很有道理。你所说的将在使用递归函数时特别有用。谢谢。 - Pedro Delfino
1
很高兴能够提供帮助。顺便说一下,这些规范被称为“类型”,我们在代码中使用函数时会根据它们来指导操作。它们可以帮助我们看清楚输入和输出内容,因此我们可以像连接匹配的电线一样进行操作。祝你好运! - Will Ness
1
是的。我曾经只在使用静态类型语言(如Standard ML)时担心类型。由于Racket是动态类型的,所以我并没有真正关注它。但是答案表使用评论来表达类型I/O是有原因的。我将开始在函数之前使用注释作为良好的实践。 - Pedro Delfino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接