vieweditattachhistorytopchangessearchhelp

第34回


■SqueakではじめるSmalltalk入門   第34回


 本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。

 前回に引き続き、コレクションの抽象クラス「Collection」のプロトコルを覗きながら、コレクションがどんなメッセージを受け付けるオブジェクトなのか、その“正体”を探ってゆきましょう。

 addingに続くarithmeticプロトコルには、前回後回しにしたadaptingに分類されるメソッド群と連携して、とてもおもしろい仕組みにより実現されたメソッドが用意されています。しかしその前に、Smalltalkのコレクションの性質の基本とも言うべきenumerating(列挙)プロトコルを先に見ておくことにします。

 さて、すでにブロックを用いた制御構造のところで触れたように、Smalltalkではいわゆるfor-nextループを次のように「メッセージ送信」のかたちで表現し、動きをシミュレートします。

World findATranscript: nil.    "トランスクリプトをアクティベート"
1 to: 5 do: [:i | Transcript cr; show: i]  "そこに1から5まで出力"


 このスクリプトは、次のように書いても同じ結果になります。字面では括弧が追加されただけですが、しかし、その意味するところは大きく違ってきます。

World findATranscript: nil.
(1 to: 5) do: [:each | Transcript cr; show: each]


 レシーバが誰で、それにどんなメッセージを送っているのか、をそれぞれについて整理すると両者の違いが明確になると思います。前者は、1に対してto:5 do: [...] というメッセージを送っているだけ、つまり、Smalltalkにおける“式”の要件であるメッセージ送信の体裁こそ満たしていますが、そうして送られるメッセージにはそれ以上(つまり式の要件を満たす以外の)“意味”を見いだすことはできません。

 他方で後者は、(1 to: 5)というコレクション(an Interval)に対して、do: [...] というメッセージを送っています。ブロック変数を「i」から「each」に書き換えたことからも察していただけるように、#do:メソッドは、それを起動したコレクションの各要素を“列挙”し、それぞれについて引数のブロックを評価します。前者より、ずっとメッセージの内容とその意味が近いところにありそうですね。また、do: [...]ならばto: n do: [...] と違って、レシーバを別のオブジェクトに置き換えることもできます。

World findATranscript: nil.
(1 to: 5) do: [:each | Transcript cr; show: each].
#(1 2 3 4 5) do: [:each | Transcript cr; show: each].
'string' do: [:each | Transcript cr; show: each]


 enumeratingプロトコルには、レシーバの各要素について繰り返しブロックを評価する#do:の他に、注目するメソッドとして、ブロックの評価値を集めてコレクションにして返す#collect:、条件に合致したものだけ集める#select:があります。なお、evenは、整数に送ってそれが偶数かを判断し、結果をtrueあるいはfalseで返させるメッセージです。

#(1 2 3 4) collect: [:each | each * 2]  " => #(2 4 6 8) "
#(1 2 3 4) select: [:each | each even]  " => #(2 4) "


 collect: [...]とselect: [...]というメッセージはたいへんよく似た、つまり、引数としてブロックをひとつ添えた形態をとっていますが、引数であるブロックの役割りはかなり違うので使用に際しては注意が必要です。前者がブロックを、新たに生じさせるコレクションの要素の“発生器”として用いるのに対し、後者ではブロックを、各要素について、新しいコレクションの要素としてよいか、それとも排除するかについての判断のために用います。したがって、collect: [...]の引数のブロックには制約はありませんが、select:[...]の引数のブロックは必ず真偽値を返さなければならないという条件を満たさなければなりません。このことは、上段右端のペインで「collect:」あるいは「select:」を選択してそれぞれの定義を読むとよりよく理解できると思います。

 select: [...]の逆の役割りを果たすreject: [...]というメッセージも使えます。もちろんこれは、引数のブロック内の式の真偽を反転させたselect:[...]と同じ結果になります(定義も、ほぼ、そうなっています)。なお、oddは整数に送ってそれが奇数かを判断し、結果を真偽(trueかfalse)で返させるメッセージです。notは、真偽に送ることでその反転を促します。

#(1 2 3 4) reject: [:each | each even]      " => #(1 3) "
#(1 2 3 4) select: [:each | each odd]       " => #(1 3) "
#(1 2 3 4) select: [:each | each even not]  " => #(1 3) "


 #collect:、#select:のように“基本”ではありませんが、覚えていて要所で使うとちょっとカッコイイのが#inject:into:です。たとえば、こんなスクリプトの場合、

| sum |
sum _ 0.
#(1 2 3 4) do: [:each | sum _ sum + each].
^ sum


…と、いうように、一時変数のsumの宣言とその初期化、また、do: [...]はレシーバを返すのでsumを改めて参照するために、合計を出すメインの式の他に、ひとつの宣言文と余計な二つの式を必要とします。#inject:into:はこうしたありがちな手続きを一時変数の力を借りずに、かつ、ひとつの式のみで済ませたいときに使います。

#(1 2 3 4) inject: 0 into: [:result :each | result + each]


 どうして、たったこれだけで合計を算出できるのか不思議に思われるかも知れませんが、そのカラクリはCollection >> #inject:into:の定義を見れば一目瞭然です。

Collection >> inject: thisValue into: binaryBlock
   | nextValue |
   nextValue _ thisValue.
   self do: [:each | nextValue _ binaryBlock value: nextValue value: each].
   ^ nextValue

このページを編集 (4874 bytes)


Congratulations! 以下の 1 ページから参照されています。

This page has been visited 827 times.