(Racket) racket/gui で年賀状宛名書き
このエントリは Lisp Advent Calendar 2014 - Qiitaの8日目の記事です。
racket/guiで年賀状印刷してみようとした。
いろいろ未完成感が漂ってますが、取りあえずこんな感じのものができました。
mnzk/nenga-print · GitHub
年賀はがき画像はこの辺から拝借
Green's Desktop greeting_cards-HowTo
「NEXT」で順繰りに表示して「PDF」でPDF出力します。用紙サイズとかあってませんが。
なんで作ろうと思ったか
racket/gui というか racket/draw ではグラフィックスの描画にデバイスコンテキスト (dc<%>インターフェース実装クラス)を使います。まあこれはよくある仕組みなんですが、このデバイスコンテキストファミリーに「pdf-dc」なるものを見つけました。
描画デバイスを抽象化するDCを使えば canvas への描画も pdf への描画も同じロジックでできてしまうんです。
ある意味当たり前のことながら、PDFまで対応しているって、ありそうであんまり無かったような気がします。
GUIでレタッチして、その結果をそのままPDFへ出力するなんてことが簡単にできそうです。実際簡単でした。
racketのOOP
classシステムは独特ですが、理解するとまぁ色々できて面白いです。
一つ分かり難く感じているのは 初期化ロジックのあたりですね。
派生クラスとスーパークラスの初期化の流れや初期値の渡し方。特に派生クラスで引数を加工してからスーパークラスへ渡すようなプログラムの書き方がイマイチよくわかりません。この辺も一度まとめないといけないかな。
Class vs Struct
どちらを使うか迷うんですが、個人的にはstructのclassに対する最大のアドバンテージはパターンマッチできるところだと思います。
- 継承やメソッドを使った実装。mixin, traitを活用 => class
- 取りあえずデータを構造化しておいて、必要な時にパターンマッチで分解 => struct
racketにはCLOSやClojureのようなマルチメソッドが無いので、structで総称みたいなことはやりにくいです。*1 その代わりにmixinやtraitでクラスに対する属性・操作をパーツ化できるようになっています。
もうちょっと・・・
UI上で、テキストユニットを選択して位置調整みたいな事もやる予定だったんですが時間切れになってしまいました。
ぼちぼちと機能追加なんかをしていくかもしれません。
データもソースに書きこんでしまってますので、外部から読み込みたいですね。
(Racket) 無名関数をcutで評価
lamda式等で無名関数を作り即時評価するコードは次のようになります。
((lambda (x y) (* x y)) 5 7) ;=> 35
このように無名関数のコードが短ければいいのですが長くなると適用される引数をずっと後ろの方に書く事になり可読性が下がります。
((lambda (x y) ... ... ... ) 5 7)
そこで srfi26 の cut を使い次のように書くと良いんじゃないかと思いました。
(require srfi/26) ((cut <> 5 7) (lambda (x y) ... ... ...))
cut のプレースホルダ <> に無名関数を渡してやるわけです。
racketではミックスイン*1は「クラスへ適用するとミックスインされた新たなクラスを返す関数」として表現されます。そしてそのような関数を作るショートカットに mixin というマクロを用意しています。つまり mixin は関数を作るマクロです。
例えば、racket/gui で幅や高さを固定したUIパーツを作るためのミックスインは次のように書くことができます。
(define (fixed-size-mixin % #:width (width -1) #:height (height -1)) ((cut <> %) (mixin (area<%>) () (super-new) (when (>= width 0) (send* this (stretchable-width #f) (min-width width))) (when (>= height 0) (send* this (stretchable-height #f) (min-height height))))))
引数 % はミックスインを適用するターゲットクラス。 #:XXXX はキーワード引数です。
mixinマクロが作るミックスイン関数をターゲットクラス % へ適用しています。
cutを使わないと、% をmixinフォームの後ろに書くにことになりますが、それはちょっと読みにくいですよね。
記事の主題からは外れますが、上のミックスインの使い方も一応書いておきます。ミックスインでクラスを定義する例です。
;; 垂直パネルを幅だけ100に固定 (define my-panel% (fixed-size-mixin vertical-panel% #:width 100)) ;; サイズ 100x200 のキャンバス派生クラスを定義 (define my-canvas% (fixed-size-mixin #:width 100 #:height 200 (class canvas% (super-new) (define/override (on-paint) ...))))
area<%>インターフェースを実装しているクラスであれば、簡単にサイズ固定できます。*2
(Racket) values->list マクロとか
無いっぽいので...
(define-syntax values->list (syntax-rules () ((_ vs) (call-with-values (thunk vs) list)))) (define-syntax values->vector (syntax-rules () ((_ vs) (call-with-values (thunk vs) vector)))) (define-syntax values->hash (syntax-rules () ((_ vs) (call-with-values (thunk vs) hash)))) (define-syntax values->struct (syntax-rules () ((_ struct-type vs) (apply struct-type (call-with-values (thunk vs) list)))))
つかう
(values->list (values 10 20)) ;=> (10 20) (values->vector (values 10 20)) ;=> #(10 20) (values->hash (values 'a 10 'b 20)) ;=> #hash((b . 20) (a . 10)) (struct person (name age email) #:transparent) (values->struct person (values "Yamada" 42 "hoge@foo.bar")) ;;=> #(person "Yamada" 42 "hoge@foo.bar")