ちょっと遊んでみる

RacketというSchemeの処理系・開発環境があり、それをインストールすると、FrTimeという描画環境と統合されたReactive Programigライブラリとそのデモがはじめからついてくる。

僕は、UbuntuWindowsにインストールしている。Windowsのほうは何の支障もなく簡単にインストールできた。Ubuntuのほうは、ソースからコンパイルしたので、なんかX11関連(?)のライブラリが要求されたが、Synapticでそのライブラリを見つけて解決。

で、デモのmouse.rktなどを参照しつつ、FrTimeで少し遊んでみる。

マウスポインタへの追従

まず、非常に単純なコード。これで、画面に青い円が表示される:

#lang frtime
(require frtime/animation)

(define circle-pos (make-posn 10 10))
(define circle-radius 10)
(define circle-color "blue")

(display-shapes
 (list
  (make-circle circle-pos circle-radius circle-color)))

円をマウスポインタに追従させるには、circle-posをmouse-posで定義する:

#lang frtime
(require frtime/animation)

(define circle-pos mouse-pos)
(define circle-radius 10)
(define circle-color "blue")

(display-shapes
 (list
  (make-circle circle-pos circle-radius circle-color)))

このmouse-posにdelay-byを適用すると、円(circle-pos)がマウスポインタ(mouse-pos)の動きに少し遅れるようになる:

#lang frtime
(require frtime/animation)

(define circle-pos (delay-by mouse-pos 100))
(define circle-radius 10)
(define circle-color "blue")

(display-shapes
 (list
  (make-circle circle-pos circle-radius circle-color)))

時間の表示

まず、次のコードで、画面に「100」と表示される:

#lang frtime
(require frtime/animation)

(define number 100)

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" number)
                     "black")))

この表示を現在のエポック秒に切り替えてみる:

#lang frtime
(require frtime/animation)

(define number seconds)

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" number)
                     "black")))

これで、刻々と変化するエポック秒が表示される。数値演算を行うこともできる:

#lang frtime
(require frtime/animation)

(define number (/ seconds 10.0))

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" number)
                     "black")))

プログラムの開始時間をとるためには、snapshot関数を使う:

#lang frtime
(require frtime/animation)

(define start-time (snapshot (seconds) seconds))
(define number start-time)

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" number)
                     "black")))

で、─ここが面白い─プログラムの開始からの経過秒をとるためには、seconds - start-timeをとれば良い:

#lang frtime
(require frtime/animation)

(define start-time (snapshot (seconds) seconds))
(define number (- seconds start-time))

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" number)
                     "black")))

マウスクリックへの反応

今までのマウスポインタ位置(mouse-pos)やエポック秒(seconds)は、どちらもbehaviorだった:

> (behavior? mouse-pos)
#t
> (behavior? seconds)
#t

behaviorというのは刻々と変化する値を意味する*1。刻々と変化する値だから、常に何らかの値を持っている。

しかし、FrTimeでは(またたぶん大部分のRP環境では)、マウスクリックはbehaviorとは異なるもの、eventで表現される。これはある瞬間にだけある値を発信(?)するもので、常に何らかの値を示しているわけではない。

とりあえず、動くコード:

#lang racket
(require frtime frtime/animation)

(define value (switch left-clicks "not-clicked"))

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     (format "~a" value)
                     "black")))

left-clicksがevent。画面上でクリックすると、「not-clicked」という表示が変わる。もうちょっと手の込んだことをしよう:

#lang racket
(require frtime frtime/animation)

(define clicked-pos
  (left-clicks . ==> . (lambda (e) (format "(~a, ~a)"
                                           (send e get-x)
                                           (send e get-y)))))

(define message (switch clicked-pos "not-clicked"))

(display-shapes
 (list
  (make-graph-string (make-posn 20 20)
                     message
                     "black")))

これで、クリックした場所(x, y)が表示されるようになる。

eventの取扱い

このbehaviorとeventは抽象的にきちんと定義しようとすると面倒そう。

Yampaだと、behaviorの概念は、現在時刻(?)から値を返す関数とされている。たぶんこれでも不正確で、behaviorはプロセスが置かれている現在の環境すべてから値を返す関数だと考えるのが、もっともらしいように思える。eventは、Yampaでは、空であることもあるコンテナのbehaviorだとされているが、これはしっくりくる。

個人的には、eventはbehaviorの一種だというYampaの取扱いが統一的で良さそうに思うけど、とりあえずFrTimeではそうなっていない。behaviorに使える関数は、必ずしもeventに使えるわけではない。

また、これはbehaviorとeventを厳格に区別することの帰結ではないけれども、eventから何かを表示させようとすると、それを何らかの形でbehaviorに変換しなくてはならない。eventは少なくとも概念上は本当に一瞬のシグナルを出すだけだから、かりにそれを表示したとしても、人間には見えないよね。

eventをbehaviorに変換する関数には、hold、switch、accum-b、collect-bがある。

*1:「刻々と変化する値」というのは、さらっと書いたが、よく考えると意味の分からない表現だ。後述。