RubyやHaskellの部分適用っぽいものを引数にとるメソッド呼び出しをSqueak Smalltalkで

先週末の Ruby勉強会@札幌-22 にオーストラリアから参加されたアンドリュー・グリムさん の発表で知った Ruby の技。

本日の収穫 #ruby: [1,2,3].map(&1.method(:+)) => [2,3,4]。
Haskellで言う所の map (+1) [1,2,3]。

@tmaeda


この発想はなかった。w

その場で Smalltalk でも!というリクエストはいただいたのですが、残念ながら Smalltalk のメッセージングを介したメソッドコールには、Ruby のバウンドメソッドや Haskell などの部分適用のような概念がないので再現できませんでした。


ちょっとくやしかったので、こんなふうなそれっぽく見えるものを考えてみました。

#(1 2 3) collect: #(+ 1)   "=> #(2 3 4) "


仕込みは簡単。SequenceableCollection に次のような #value: を定義しておくだけです。

SequenceableCollection >> value: rcvr
   ^rcvr perform: self first withArguments: self allButFirst


こんなこともできます。

#(1 2 3) collect: #(between:and: 3 4)   "=> #(false false true) "
#(1 2 3) detect: #(between:and: 3 4)   "=> 3 "


はたして、次のような普通の書き方と比べてどのくらい嬉しいのかという問題は大いにありそうですが。^^;

#(1 2 3) detect: [:x | x between: 3 and: 4]   "=> 3 "


以下、簡単な解説。

Ruby ではブロック付きメソッド呼び出しの引数に & を付けるとその引数に #to_proc を呼ばせることでブロックに代えることができますが、Smalltalk ではそもそもブロックが第一級なので引数である(通常は)ブロックに value: element というメッセージが送られる仕組みであるため、ブロックの代わりの引数にブロック以外の何かを使いたければそのオブジェクトが value: element というメッセージを適切に処理できるように(通常であれば、メソッドとして #value: を適切内容で定義)しておけばよいわけです。


たとえば Ruby

[1,2,3].collect(&:to_s)   #=> ["1","2","3"]


に似せた、

#(1 2 3) collect: #asString  "=> #('1' '2' '3') "


であれば、#asString のクラスである Symbol に #value: が定義されていればよい、というふうな感じです。

Symbol >> value: anObject 
   ^anObject perform: self.