Code Aquarium

minazoko's blog -*- 水底のブログ -*-

(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

*1:「ミキシン」と呼ぶ方が正しいらしい。

*2:racket/guiではpanel%やcanvas%等のクラスはデフォルトで親コンテナにオートフィットする設定になっています。

(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")

(Racket) member-keyの話

とりあえずクラスの基本

racketではクラスは次のように作成します。

(define sub-class%
  (class super-class%
    (super-new)
    ...))

racketのクラスは単一継承で必ずなんらかのクラスを継承しなければなりません。この例では super-class%クラスを継承した sub-class%クラスを生成しています。
特に継承したいクラスがなければ、object%を継承します。object%クラスは全てのクラスのトップレベルクラスです。
super-newは親クラスのコンストラクタ呼び出しでこれも必須です。
クラス名は % をサフィックスするのが慣例になっています。

class キーワードの後ろが「作りたいクラス名」ではなく「スーパークラス」を書くところが独特ですね。
とりあえずこれだけ覚えておけば、あとはフィーリングでなんとかなるはず。
実例を見てみましょう。

;;クラスを作成
(define greeter%
  (class object%
    (super-new)
    (define/public (greet) 
      "hello")))

(define greeter (new greeter%))  ;; インスタンスを生成
(send greeter greet)  ;=> hello  ;; メソッドコール

ここでは new でインスタンスを作っていますが make-objectでも作れます[*1]。
以上。基本の説明終わり。あとはフィーリングで。

クラスを作る関数

動的な言語なのでクラスは実行時に作られます。
「クラスを作る関数」を作るのも簡単です。

;;クラスを作る関数
(define (make-greeter-class)
  (class object%
    (super-new)
    (define/public (greet) 
      "hello")))

(define greeter% (make-greeter-class))  ;; クラスを生成
(define greeter (new greeter%))
(send greeter greet)  ;=> hello

特に問題はありませんね。

外部名の作成

クラスのメンバーを別名で外部公開することができます。

(define (make-greeter-class)
  (define-member-name greet (member-name-key aisatu))
  (class object%
    (super-new)
    (define/public (greet) 
      "hello")))

(define greeter% (make-greeter-class))
(define greeter (new greeter%))
(send greeter aisatu) ;=> hello
(send greeter greet)  ;=> error

define-member-nameの第一引数で指定したメンバー名がレキシカルスコープの外では第二引数で指定したメンバーキーに置き換えられます。第二引数は単なるシンボルではなく membar-keyというオブジェクトである必要があります。

(member-name-key aisatu)  ;=> #<member-key:aisatu>

別名で外部公開するとスコープ外では本来のメンバー名 greet ではアクセスできなくなります。
一方レキシカルスコープ内では、元の greet と 別名の aisatu のどちらでも参照することができます。

特定クラス間でのみのメンバー共有

generate-member-key というマクロも member-keyを作ります。このマクロは引数を取らず、gensym で作成したランダムなシンボルを持つmember-keyを生成します。
この機能を利用してレキシカルスコープ内で作成したクラス間でのみ参照可能なメンバーを作れます。

(generate-member-key) ; => #<member-key:member813>

このメンバーキーで define-member-name するとヒューマンアンリーダブルな別名で外部公開することになり「それなら外部ではまず参照できないよね」というpythonのようなやりかたで外部非公開(のようなものに)しています。
この実例が前のエントリになります。前エントリでは let でスコープを作っていますが レキシカルスコープができれば let でも lambda でも define でもなんでもよいです。

追記 define-local-member-name

もっと直接的にメンバーをローカル化し外部に隠すフォームがありました。

(define (make-hoge-class)
  (define-local-member-name hoge) ;=> メンバーhogeをローカル化
  (class object% (super-new)
    (define/public (hoge) 'hoge)
    (define/public (fuga) (hoge))))

(define h (new (make-hoge-class)))
(send h fuga)   ;=> hoge
(send h hoge)   ;=> error

*1:newとmake-objectはコンストラクタ引数がある場合に使い分けます。new はキーワードパラメータ、make-objectは位置パラメータで引数に適用します