無限に対応するクロック

Guile [cite{guile}] の手続き alarm は割り込み可能なタイマ機構を 提供しています。ユーザはいくらかの時間単位後のアラームを設定したり 解除したりできます。また、止めることもできます。アラームのタイマが 自分の持ち時間を使い切れば、アラームはオフにセットされます。これが 重要なのは、ユーザが設定可能であるということです。Guile の alarm15.1 節のクロックとは完全に同じものというわけでは ありません。しかし、簡単に変更することが可能です。

この alarm のタイマは、最初は停止しているか、あるいは、 静止しています。すなわち、時間が過ぎも、アラームを オフにセットすることはありません。アラームのアラームが発生するまでの 時間を n 秒にセットするためには、ここでは n0 ではない としますが、(alarm n) を走らせます。タイマがすでに設定されているなら (そして、アラームがオフになっていないなら)、(alarm n) の 手続き呼び出しは以前に設定されたときからの残りの秒数を返します。 もし、以前にアラームが設定されていなければ、(alarm n)0 を 返します。

手続き呼び出し (alarm 0) はアラームのタイマを停止します。 すなわち、時間のカウントダウンは停止します。タイマは静止し、 アラームはどれもオフにはなりません。(alarm 0) は、もしあれば、 以前のアラームの設定からの残り秒数も返します。

デフォルトでは、アラームのカウントダウンが 0 に到達したら、 Guile はコンソールにメッセージを表示して抜けてしまいます。 もっと有用な振舞いは、手続き sigaction を以下のように 使うことで得られます。

(sigaction SIGALRM
  (lambda (sig)
    (display "Signal ")
    (display sig)
    (display " raised.  Continuing...")
    (newline)))

第一引数 SIGALRM (これは q14 になっています) は sigaction に 設定が必要なものが、アラームハンドラであることを確認させます。 1 第二引数は、ユーザが選択した一引数のアラームハンドリング手続きです。 この例では、alarm がとりのぞかれたとき、ハンドラは Scheme を 抜けることなく、コンソールに "Signal 14 raised. Continuing..." を表示します。(14 はアラームがハンドラに渡す SIGALRM の値です。 この値について今は心配する必要はありません。)

ここでの利用の観点からは、この単純なタイマのメカニズムには、 ひとつ問題があります。手続き alarm の呼び出しで返される値 0 が曖昧であるということです。この値は、alarm が静止していることを 意味する場合と、時間切れになったことを意味する場合があります。 この曖昧さは、「*infinity*」をアラームの算術演算に含めることが できれば、可能になります。言い換えれば、静止したクロックは *infinity* 秒をもつものであることを除けば、alarm のように働く クロックが欲しいのです。これにより、いろいろなことが自然な ものになります。

(1) 静止したクロック上の (clock n)0 ではなく、 *infinity*を返す。

(2) クロックを止めるには、(clock 0) ではなく (clock *infinity*) を呼ぶ。

(3) (clock 0) は無限小時間を設定されたクロックに設定するのと 同等で、即時にアラームを起す。

Guile では *infinity* は以下のような「数値」として定義できます。

(define *infinity* (/ 1 0))

clockalarm を使って定義できます。

(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*)
                  ;これはクロックを止めることと同等。
                  ;これはもし、クロックがすでに止っていたら
		  ; *infinity* を返すことをのぞけば、
		  ; (alarm 0) とほぼ同等。

                  (let ((time-remaining (alarm 0)))
                    (if stopped? *infinity*
                        (begin (set! stopped? #t)
                          time-remaining))))

                 ((= val 0)
                  ;これは、アラームを直ちに発生する設定と同等。
                  ;これは、アラームハンドラを起こすことをのぞけば、
                  ;(alarm 0) とほぼ同等。

                  (let ((time-remaining (alarm 0)))
                    (if stopped?
                        (begin (generate-clock-interrupt)
                          *infinity*)
                        (begin (generate-clock-interrupt)
                          time-remaining))))

                 (else
		  ;これは n != 0 の場合の (alarm n) と同等。
                  ;ただし、クロックがこの前で静止状態になっていれば、
		  ;*infinity* を返すことを思いだすこと。

                  (let ((time-remaining (alarm val)))
                    (if stopped?
                        (begin (set! stopped? #f) *infinity*)
                        time-remaining))))))))))

clock 手続きは 3 つの内部状態変数を使います。

(1) stopped? はクロックが停止しているかどうかを示します。

(2) clock-interrupt-handler はユーザの指定したアラームハンドラの 動作部分をあらわすサンクです。

(3) generate-clock-interrupt はユーザの指定したアラームハンドラを 走らせる前に stopped? を偽に設定する、もうひとつのサンクです。

clock 手続きはふたつの引数を取ります。最初の 引数が set-handler であれば、アラームハンドラと して二番目の引数を使います。

最初の引数が set なら、アラームまでの時間を、 二番目の引数のあたいに設定し、前回の設定からの 残り時間を返します。このコードは 0*infinity*、 および、他の値を時間をあらわすのに別々に使います。 これによりユーザは、alarm への数学的に透明な インタフェースが得られます。


1 他にもシグナルはあって、それぞれ対応するハンドラがあり、 sigaction はそれらをセットすることもできます。