みなさんこんにちは、@tomoodaです。 おかげさまでこのブログも前3記事へのページビューが1/2Kを越えました。 思った以上に読んでもらえて、嬉しいかぎりです。
さて、今回のテーマは、手続き型ベースのプログラミング言語での「代入」についてクッチャベります。ツ
変数という概念を数学で学んだ時には、「代入」とは、書き換えでした。 例えば、f(x) = a * x + bという式があった時、a=2, b = 3を「代入」すると、それぞれ前記の式の中の全てのaを2、bを3に「書き換え」て、f(x) = 2 * x + 3、としていましたね。
しかし、プログラミング言語、特に手続き型ベースのプログラミング言語では、代入は2種類あります。 それが破壊的代入と名前束縛です。
破壊的代入とは、ありていに言えば、代入文です。 f(x) = a * x + bについてaに2, bに3を代入して計算を進めていきながら、ある所でaやbに別の値を代入したりします。 同じ式f(0)を計算しても、aに2, bに3が代入された時の値と、aもbも0が代入された時とでは、f(0)の値が違ってしまいます。 そんなこともあって、いわゆる関数型言語など、破壊的代入は悪だとするプログラミングの流儀もあります。
数学でも同じ変数に何度も代入することがあります。1つは、関数適用です。 f(4)を計算した後でf(5)を計算することは、よくありますね。 xに代入する値を変えているわけです。 では、これは破壊的代入でしょうか?そんなことはありませんね。 xに4を代入した結果を計算している途中で更にxに5を代入し直すわけではありません。 xに4を代入するのとは別に、元の式に対して、xに5を代入しているわけです。 1つのコンテキストの中で書き換えるのではなく、別々のコンテキストの中で書き換えています。 これは名前束縛で、xという変数を4に束縛する、5に束縛する、と言います。
では、破壊的代入は悪なのでしょうか?撲滅すべき存在でしょうか? 薬の副作用と同じ意味で、破壊的代入を副作用の1つと見做して撲滅すべきだという主張もあります。 そう思う人達の言い分はもっともだし、撲滅したらしたでメリットはあるでしょう。 実際、オレも関数型言語は大好きです。 それでも、オレ的には撲滅すべきだと断ずるつもりは毛頭ありません。ツ
完全に副作用のない世界を追求するのも大事ですが、それ以上に、副作用の「実害を減らす」ことが大事だと思っているのですよ。
では、実害を減らすにはどうしましょうか? それにはまず、「対象を認識する」ことです。 そう、オブジェクト指向って何?で挙げた、一番大事なことです。 この場合、「対象を認識する」とは、破壊的代入と名前束縛をきちんと区別することです。 もう少し言えば、手続きの中で本質的に破壊的代入としてきちんと表現したい「代入」と、一時的に名前をつけておくために「代入」している本来「名前束縛」であるものを、表現としてきちんと書き分け、読み分ける、ということです。
そこで、手続き型ベースのオブジェクト指向言語のSmalltalkでやってみましょう。ツ 処理系としてはPharoを使います。 やることは簡単、2つのメソッドを追加するだけです。
まずは、Objectクラスに以下のメソッドを定義します。
=>> aBlock ^ aBlock value: selfさらに、Arrayクラスに
=>=> aBlock ^ aBlock valueWithArguments: selfも定義してみましょう。 たったこれだけで、破壊的代入と名前束縛を分離して、コードの見通しがスッキリします。
例えば、ネットワークプロトコルの単体テストのsetUpを書いてみます。 単体テストの対象はMyProtocolクラスで、単体テストをMyProtocolTestクラスに記述します。 MyProtocolTestクラスにはインスタンス変数としてpeer1, peer2が宣言されているとします。 また、テスト用にTCPコネクションの両端をシミュレートするクラスMyTestTCPConnectionがあるとします。
まずは、使用前。
setUp | connection stream | connection := MyTestTCPConnection new stream := connection stream1. peer1 := MyProtocol on: stream. stream := connection stream2. peer2 := MyProtocol on: stream.
では、使用後。
setUp MyTestTCPConnection new =>> [ :connection | connection stream1 =>> [ :stream1 | peer1 := MyProtocol on: stream1 ]. connection stream2 =>> [ :stream2 | peer2 := MyProtocol on: stream2 ] ]さらに、こんな書き方も。
setUp MyTestTCPConnection new =>> [ :connection | {(connection stream1). (connection stream2)} =>=> [ :stream1 :stream2 | peer1 := MyProtocol on: stream1. peer2 := MyProtocol on: stream2 ] ]
どうでしょう? 名前束縛のシンボル「=>>」と「=>=>」と、代入のシンボル「 :=」を区別すると、こんなにプログラムの構造が整理されます。
元々Pharoには=>>と全く同じ働きをするin:メッセージがありましたが、ビジュアルな効果を期待して、あえてシンボルで定義してみました。 プログラムの構造、名前束縛という「対象を認識する」ということを大事にする、それがオブジェクト指向プログラミングなのです。ツ
たった4行のプログラミングであたかも言語仕様が拡張されたかのような、視覚/意味/スタイルへの影響を誘導できる。 Smalltalkって本当に面白いですね! ツ
0 件のコメント:
コメントを投稿