大人のためのブラックボックス読解講座――クロージャとオブジェクトの微妙な関係プログラミング言語の進化を追え(2/2 ページ)

» 2007年03月29日 15時30分 公開
[川合史朗,ITmedia]
前のページへ 1|2       

メッセージ

 「環境」を「状態」と見なせば、クロージャは「状態を持つ関数」となります。リスト1に挙げるmake-boxは、引数valueを閉じ込むクロージャdispatchを返します。実行例1のようにvalueに具体的な値を与えれば、その値を閉じ込んだクロージャが得られます。

(define (make-box value)

  (define (dispatch msg)

    (case msg

      ((get)

        (lambda () value))

      ((set)

        (lambda (newval) (set! value newval)))))

  dispatch)


リスト1 引数valueを閉じ込むクロージャdispatchを返すmake-box

gosh> (define a-box (make-box 10))

↑引数を「10」としてmsg-boxを実行し、結果をa-boxに束縛する

a-box

gosh> a-box

#<closure (make-box dispatch)>

↑a-boxには、クロージャが束縛された


実行例1 make-boxに値を渡し、クロージャを得る

 このa-boxはクロージャdispatchです。dispatch自体は引数msgを取り、その値がgetであれば、

(lambda () value)


すなわち「閉じ込まれたvalueを返すクロージャ」を、その値がsetであれば、

(lambda (newval) (set! value newval))


すなわち「引数newvalを取り、閉じ込まれたvalueの値をそれに変更するクロージャ」を、それぞれ返します。試しに実行してみましょう。

gosh> (a-box 'get) ←引数msgとしてgetを渡した

#<closure (make-box dispatch dispatch)>

↑クロージャが得られた

gosh> (a-box 'set) ←引数msgとしてsetを渡した

#<closure (make-box dispatch dispatch)>

↑クロージャが得られた


 「(a-box 'get)」、「(a-box 'set)」の戻り値はクロージャですから、それを呼び出せばアクションを起こすことができます。括弧がネストして見にくいので、これらを[a-box'get]、[a-box'set]のように表記してみましょう。多くのScheme処理系では、大括弧[]を()と同様に使うことができます。

gosh> (define a-box (make-box 10))

a-box

gosh> ([a-box'get])

10

gosh> ([a-box'set] 11)

11

gosh> ([a-box'get])

11


 この手法は、dispatchが判別するメッセージの種類とその内容さえ変えれば、いろいろと応用が利きます。dispatchの本体は定型なので、これをマクロで書いてみましょう。

(define-syntax object-maker

  (syntax-rules ()

    [(object-maker (ivar ...) 

                   (method args . body) ...)

     (lambda (ivar ...)

       (define (dispatch msg)

         (case msg 

           ((method) (lambda args . body)) ...))

       dispatch)]

    ))


リスト2 dispatchの本体を書きやすくするためのマクロ。3、4行目のようなパターン(式)があるなら、5行目以降のように式を変換するというもの

 リスト2の定義は、

(object-maker (ivar ...) (method args . body) ...)


という式が出てきたらそれを、

(lambda (ivar ...) 以下略)


という式に変換せよ、ということです。一部括弧に[]を使っているのは見やすさのためで、()を使うのと意味的な違いはありません。また、プログラム中の「...」は説明のために省略したわけではなく、れっきとしたSchemeの構文要素です。「(x ...)」で、「xが0個以上繰り返される」ということを示しています。

 このマクロを使えば、リスト1のmake-boxは次のように書くことができます。

(define make-box

  (object-maker (value)

     [get () value]

     [set (newval) (set! value newval) value]))


 構文[a-box'get]や[a-box'set]を、a-box.getやa-box.setに読みかえると分かりやすくなりますが、make-boxが返す「もの」は、

  1. 状態を持ち
  2. メッセージを受け取って状態を変えたりアクションを起こす

という意味では、オブジェクト指向における「オブジェクト」の基本的な性質*を持っていると言えます。このメカニズムを基本に、継承やクラスといった、主流のオブジェクト指向言語に備わっている機能を足してゆくことは難しくありません*

クロージャ≡オブジェクト(mod構文)

 前節を読んで、「単にオブジェクトっぽいものを、クロージャを使って作ってみせただけではないか」と思われたかもしれません。では、make-boxが返す「何か」と、あなたが考える「オブジェクト」との本質的な違いは何でしょうか。

 「言語としてネイティブでサポートされているか否か」というのは、言語を使う上での利便性には大きく影響します。しかしそれは、書かれたプログラムの意味に本質的な影響を与えるものではありません。

 前節のmake-boxの定義は、オブジェクトの1つの実装であると同時に、「メッセージベースのオブジェクトシステム」の1つの仕様定義であると考えることもできます。make-boxが返す「何か」とは、前節で与えられた仕様を満たす「何か」です(それが実際に前節のコードで実装されているかどうかにかかわらず)。その「何か」は固有の状態を持ち、メッセージを受け取ってアクションを起こします。ある人はそれをクロージャと呼び、ある人はそれをオブジェクトと呼ぶでしょう。

 幾つかあるオブジェクト指向の定義のうち、少なくとも「状態と処理が合わさっていて、メッセージを送ることでアクションを起こさせるもの」という定義に関して、クロージャとオブジェクトの違いはせいぜい構文の問題にすぎません(もちろん構文は言語の使い勝手に大きな影響を与えますが、マクロの例を示したように、表面的に処理できるものでもあります)。

 ……でも、そう言いきられても素直に納得できない読者が多いのではないでしょうか。何か、反論したいようなもやもやを感じるのではないですか? そのもやもやとは何なのでしょう。次回はこの点について解説していきます。

このページで出てきた専門用語

オブジェクト指向における「オブジェクト」の基本的な性質

何をもってオブジェクト指向とするかについては、唯一の定義といったものは存在しない。Jonathan Reesのメモでは、幾つかの機能や特性を挙げ、人によって異なるサブセットをオブジェクト指向の定義としている、と述べている。

主流のオブジェクト指向言語に備わっている機能を足してゆくことは難しくありません

継承は、dispatchが認識できないメッセージに出合ったら「親オブジェクト」に処理を移譲すれば良い。またクラスは、「ある特定のプロトコルを満たすオブジェクト」として実装できる。なお、クラスや継承、メソッド定義構文を持った、より完全なオブジェクトシステムをクロージャ上で構築する方法については、例えばKen Dickeyの「Scheming with objects」*を参照すると良いだろう。

「Scheming with objects」

Ken Dickey: "Schmeing with Objects", Computer Language, October 1992.

ftp://ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/swob.txt


本記事は、オープンソースマガジン2006年10月号「プログラミング言語の進化を追え」を再構成したものです。


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ