Ruby1.8.7 の新機能を Squeak Smalltalk にマップしてみる


Ruby 1.8.7で使えるようになったRuby 1.9のメソッドたち - http://rubikitch.com/に移転しました によれば、Ruby1.8.7 には、これまで Ruby1.9 でしか使えなかった機能がかなり豊富に盛り込まれたようです。そこで、列挙されているもののうち、Squeak Smalltalk(Squeak3.9)にて相当、あるいは関連ありそうな機能をピックアップして対応させてみました。


▼ ブロックにブロックを渡す

もとより Smalltalk のブロックは Ruby のブロックと異なり、引数として特別扱いされません。したがって、レシーバがブロックでも通常どおり普通にブロックを渡せます(Smalltalk のブロックは第一級オブジェクト)し、逆に Ruby と違い常にそうする必要があります(“ブロック付きメソッド呼び出し”のような特殊な構文はない)。

[:block | block value] value: ['block is passed']   "=> 'block is passed' "

▼ Object#tap

Ruby の Object#tap は Squeak Smalltalk では Object>>#in: に相当します。ただ、Object#tap は無条件でレシーバを返すのに対し、#in: はブロックの返値をそのまま返すので、うれしさは半分以下ですね。

Dictionary new in: [:dict | dict at: #one put: 1; at: #two put: 2]; yourself
"=> a Dictionary(#one->1 #two->2 ) "

▼ Binding#eval

Squeak Smalltalk で実行コンテキストを指定して式を評価するのには #evaluate:in:to: が使えます。コンテキストをレシーバにして云々…という機能は見あたりませんでした。

| context |
context := [| var | var := #inBlock] value; yourself.
^Compiler new evaluate: 'var' in: context to: self   "=> #inBlock "

▼ __method__

実行中のメソッド名は、実行中のコンテキストを得る thisContext という擬似変数から selector でたぐれます(Smalltalk ではメソッド名をセレクタと呼びます)。

Object >> foo
    ^thisContext selector
Object new foo   "=> #foo "

▼ Method#name

CompiledMethod>>#selector が相当します。

(Collection compiledMethodAt: #size) selector   "=> #size "

▼ Method#owner

#methodClass が相当します。

(Collection compiledMethodAt: #size) methodClass   "=> Collection "

▼ 平滑化レベル指定付き Array#flatten

残念ながら Squeak Smalltalk の #concatenation は、第一層固定のようです。

#((1 2 3) (4 5) (6)) concatenation   "=> #(1 2 3 4 5 6) "

▼Array#shuffle

#shuffled が相当します。破壊的操作版はありません。

#(1 2 3 4 5) shuffled   "=> #(3 4 5 1 2) "

▼ Array#choice

#atRandom が相当します。

#(1 2 3 4 5) atRandom   "=> 5 "

▼ Array#permutation

#permutationsDo: が相当します。

#(1 2 3) permutationsDo: [:perm | Transcript cr; show: perm]

▼ Array#combination

#combinations:atATimeDo: が相当します。

#(1 2 3 4) combinations: 3 atATimeDo: [:comb | Transcript cr; show: comb]

▼ Array#pop、Array#shift

OrderedCollection>>#removeFirst: 、#removeLast: が相当します。

| colln |
colln := #(1 2 3 4) asOrderedCollection.
colln removeFirst: 2.
^colln   "=> an OrderedCollection(3 4) "
| colln |
colln := #(1 2 3 4) asOrderedCollection.
colln removeLast: 2.
^colln   "=> an OrderedCollection(1 2) "

▼ブロック付き Array#index、Array#rindex

#findFirst: 、#findLast: が相当します。

#(0 1 0 1 0) findFirst [:each | each > 0]   "=> 2 "
#(0 1 0 1 0) findLast: [:each | each > 0]   "=> 4 "

▼ Array#take

#first: が相当します。

#(1 2 3 4) first: 2   "=> #(1 2) "

▼ Array#drop

#allButFirst: が相当します。

#(1 2 3) allButFirst: 2   "=> #(3) "

▼ Enumerable#none?

#noneSatisfy: が相当します。

#(ant bear cat) noneSatisfy: [:each | each size = 5]   "=> true "

▼ Enumerable#min_by、Enumerable#max_by

#detectMin: 、#detectMax: が相当します。

#(18 15 22 53) detectMin: [:each | each \\ 10]   "=> 22 "
#(18 15 22 53) detectMax: [:each | each \\ 10]   "=> 18 "

▼ Enumerable#find_index

#findFirst: が相当します。

(1 to: 100) findFirst: [:each | (each isDivisibleBy: 5) and: [each isDivisibleBy: 7]]   "=> 35 "

▼ Enumerable#inject で二項メソッド名を指定可能に

相当する機能はありません。ただ、合計には #sum が使えます。

(1..4).inject(0){ |s,e| s+e }   #=> 10
(1..4).inject(:+)   #=> 10
(1 to: 4) inject: 0 into: [:sum :each | sum + each]   "=> 10 "
(1 to: 4) sum   "=> 10 "


なお、Smalltalk でも、Symbol>>#asBlock もしくは、Symbol>>#value:value: を定義することで、Ruby の Symbol#to_proc モドキのことは実現可能です。念のため。

Symbol >> asBlock
    ^[:rcvr :arg | rcvr perform: self with: arg]
(1 to: 4) inject: 0 into: #+ asBlock   "=> 10 "
Symbol >> value: rcvr value: arg
    ^rcvr perform: self with: arg
(1 to: 4) inject: 0 into: #+   "=> 10 "

▼ Enumerable#count

#occurrencesOf:(要素指定の場合)、#count:(ブロック指定の場合)に相当します。

#(1 2 4 2) occurrencesOf: 2   "=> 2 "
#(1 2 4 2) count: [:each | each isDivisibleBy: 2]   "=> 3 "

▼ Enumerable#group_by

#groupBy:having: が相当します。

(1 to: 6) groupBy: [:each | each \\ 3] having: [:group | true]
"=>  a PluggableDictionary(
    0->an OrderedCollection(3 6)
    1->an OrderedCollection(1 4)
    2->an OrderedCollection(2 5) ) "

▼ Enumerable::Enumerator#next

Ruby の Enumerator は、Smalltalk の ReadStream とよく似た挙動を呈します。したがって、#next が相当すると考えてよさそうです。

| stream |
stream := (1 to: 6) readStream.
^{stream next. stream next}   "=> #(1 2) "

▼ Enumerable::Enumerator#rewind

同様に、#reset が相当します。

| stream |
stream := (1 to: 6) readStream.
stream next; next.
stream reset.
^{stream next. stream next}   "=> #(1 2) "

▼ String#lines、String#bytes

Squeak Smalltalk では、どちらかというと、これまでの Ruby1.8 寄りで #linesDo:、(必要なら asByteArray してから)#do: を使います。

| stream |
stream := #() writeStream.
'abc\def\' withCRs linesDo: [:each | stream nextPut: each].
^stream contents   "=> #('abc' 'def') "
| stream |
stream := #() writeStream.
'abc' asByteArray do: [:each | stream nextPut: each].
^stream contents   "=> #(97 98 99) "

▼ String#chars、String#each_char

もとより Squeak Smalltalk では #do:(Ruby の #each に相当)は文字単位です。

'おはよう' as: Array   "=> #($お $は $よ $う) "
OrderedCollection new in: [:colln | 'おはよう' do: [:each | colln add: each]]; yourself
"=> an OrderedCollection($お $は $よ $う) "

▼ String#start_with?、String#end_with?

#beginsWith: 、#endsWith: が相当します。

'あいうえお' beginsWith: 'あい'   "=> true "
'あいうえお' endsWith: 'お'    "=> true "

▼Range#step

Smalltalk では範囲オブジェクトのステップは生成時に指定します。

(1 to: 6 by: 2) asArray   "=> #(1 3 5) "

▼ Integer#odd?、Integer#even?

#odd、#even が相当します。

1 odd   "=> true "
1 even   "=> false "
12 odd   "=> false "
12 even   "=> true "

▼ 0 の累乗

Squeak Smalltalk で累乗は #raisedTo: です。0 ** 0 は 1 を返しますが、0 ** -1 は ZeroDivide エラー。

0 raisedTo: 0   "=> 1 "
0 raisedTo: 0.0   "=> 1 "
0.0 raisedTo: 0   "=> 1.0 "
0 raisedTo: -1   "=> ZeroDivide "