附錄D 可設為infinity的時鐘
====================
Guile的過程`alarm`提供了一種可中斷的定時器機制。用戶可以給這個時鐘設置或重置一些時間片,或者讓它停止。當時鐘的定時器遞減到0后,它就會執行用戶之前設定的動作。Guile的`alarm`不是一個類似于第十五章第一節里定義的那種時鐘,但是我們可以很容易的把它改造成那樣。
時鐘的定時器的初始狀態是停止的,也就是說它不會隨著時間流逝而被“觸發”。如果想把定時器的觸發時間設置為`n`秒(`n`不為0),運行`(alarm n)`。如果定時器已經被設定過了,那么`(alarm n)`就返回該定時器在本次設定前剩余的秒數。如果之前沒有設定過,則返回0。
執行`(alarm 0)`讓時鐘的定時器停止,即定時器中的計數器【你可以理解為一個變量】不會隨時間而遞減,而且不會觸發。`(alarm 0)`同樣返回定時器在本次設定前剩余的秒數(如果之前設定過的話)。
默認情況下,當時鐘的定時器計數減到0時,Guile會在控制臺上顯示一條消息并退出。更多的行為可以用過程`sigaction`來設定,如下所示:
```scheme
(sigaction SIGALRM
(lambda (sig)
(display "Signal ")
(display sig)
(display " raised. Continuing...")
(newline)))
```
第一個參數`SIGALRM`(恰好是14)告訴`sigaction`需要設定的時鐘處理函數[1]。第二個參數是一個用戶指定的單參數過程。在這個例子里,當時鐘觸發時,處理函數會在控制臺上顯示`"Signal 14 raised. Continuing..."`而不是退出Scheme(14是變量`SIGALRM`的值,時鐘會把它傳遞給它對應的處理過程,我們現在先不考慮這個)。
從我們的角度看,這種簡單的定時器機制有一個問題。過程`alarm`的返回值`0`的意義是不明確的:既可能是指時鐘處于停止狀態,也有可能是剛好計時器減到了0。我們可以通過在時鐘的算法里引入`*infinity*`來解決這個問題。換句話說,我們需要的時鐘與`alarm`基本上是差不多的,除了一點,那就是如果時鐘停止的話,那么它有`*infinity*`秒。這樣就看起來比較自然了。
1. `(clock n)`對于一個停止的時鐘返回`*infinity*`,而不是0。
2. 如果讓時鐘停止,執行`(clock *infinity*)`,而不是`(clock 0)`。
3. `(clock 0)`相當于給時鐘設置一個無限小的時間,也就是讓它立即觸發。
在Guile中,我們可以把`*infinity*`定義為如下的“數”:
```scheme
(define *infinity* (/ 1 0))
```
我們用`alarm`來定義`clock`。
```scheme
(define clock
(let ((stopped? #t)
(clock-interrupt-handler
(lambda () (error "Clock interrupt!"))))
(let ((generate-clock-interrupt
(lambda ()
(set! stopped? #t)
(clock-interrupt-handler))))
(sigaction SIGALRM
(lambda (sig) (generate-clock-interrupt)))
(lambda (msg val)
(case msg
((set-handler)
(set! clock-interrupt-handler val))
((set)
(cond ((= val *infinity*)
;This is equivalent to stopping the clock.
;This is almost equivalent to (alarm 0), except
;that if the clock is already stopped,
;return *infinity*.
(let ((time-remaining (alarm 0)))
(if stopped? *infinity*
(begin (set! stopped? #t)
time-remaining))))
((= val 0)
;This is equivalent to setting the alarm to
;go off immediately. This is almost equivalent
;to (alarm 0), except you force the alarm
;handler to run.
(let ((time-remaining (alarm 0)))
(if stopped?
(begin (generate-clock-interrupt)
*infinity*)
(begin (generate-clock-interrupt)
time-remaining))))
(else
;This is equivalent to (alarm n) for n != 0.
;Just remember to return *infinity* if the
;clock was previously quiescent.
(let ((time-remaining (alarm val)))
(if stopped?
(begin (set! stopped? #f) *infinity*)
time-remaining))))))))))
```
過程`clock`用到了三個內部狀態變量:
1. `stopped?`,表示時鐘是否是停止的;
2. `clock-interrupt-handler`,一個過程,表示用戶希望在時鐘觸發后執行的動作;
3. `generate-clock-interrupt`,另一個過程,該過程會在運行用戶定義的時鐘處理過程前把`stopped?`設為`false`。
過程`clock`有兩個參數。如果第一個參數是`set-handler`,那么就把第二個參數作為時鐘處理器。
如果第一個參數是`set`,就把該時鐘觸發時間設置為第二個參數,返回本次設定前定時器剩余的秒數。代碼對`0`、`*infinity*`以及其他時間值的處理是不同的,這樣用戶可以得到一個算術上對`alarm`透明的接口。
----------------------------------------
[1]. 還有一些其他的信號和與之相應的處理器,`sigaction`同樣可以使用它們。