注目した要素のコレクション内位置情報にアクセスする手段がないこと

ヒビルテ 経由の ブロックエクステンド をさらに経由して2ちゃんねるRuby スレにたしかに、

[1,2,3].each{|i|
  print "[",i, "," if i.first?
  print i, "]" if i.last?
  print i, "," if i.middle?
}

なんていうのがあったなぁ…と思い出したついでに、前の Python Challenge のエントリーで、コレクションの要素の最初と真ん中と最後のを別のコレクションに括り出すときやっつけで、

collection
collection _ #(a b c d e f g h i) asOrderedCollection. {collection. #(1 4 7) collect: [: nn | collection removeAt: nn]}
=>  #(an OrderedCollection(#B #C #D #F #G #H) #(#a #e #i))

なーんて仮とはいえトリッキーかつ分かりづらく書いたのをすっかり忘れて、そのままにしてしまっていたのを思い出したわけですが、これをなんとかできないかという話。(前置き長杉)


念のため前のスクリプトを、普通に手続き的かつ比較的ていねいに書けば、

collection pickedUps remains pickingIndices
collection _ #(a b c d e f g h i). pickingIndices _ {1. collection size // 2 + 1. collection size}. pickedUps _ OrderedCollection new. remains _ OrderedCollection new. collection keysAndValuesDo: [: key : value | (pickingIndices includes: key) ifTrue: [pickedUps add: value] ifFalse: [remains add: value]]. ^ {remains. pickedUps}
=> #(an OrderedCollection(#b #c #d #f #g #h) an OrderedCollection(#a #e #i))

という具合になりましょうか。


で、本題ですが Squeak システムの Smalltalk 言語には #first、#middle、#last があるので、これを使って上で“やりたいこと”をもっとうまく表現できないものか…と、つらつらと考えたりするわけです。たとえば、middle はレシーバであるコレクションの真ん中の要素を取り出すメソッド(SequenceableCollection >> #middle)を起動するメッセージで、

#(a b c d e) middle   " => #c "

というふうに使います。余興として最初のトリッキーなのにこれを使うと、

collection indices
collection _ #(a b c d e f g h i) asOrderedCollection. indices _ 1 to: collection size. {collection. (#(last middle first) collect: [: position | collection removeAt: (indices perform: position)]) reverse}

と、多少マシになります。


さて、このように注目して取り出した要素を、もとのコレクションから削除するような直接的な記述ができれば、さらにすっきりと書けそうです。ちょっと分かりづらいたとえですが、Common Lisp の SETF のようなかんじのことがしたいな、と(middle ではなく CAR で、削除ではなく NIL への置き換えになってしまいますが…)。

(LET ((L '(A B C D E))) (SETF (CAR L) NIL) L)
=> (NIL B C D E)


しかし残念ながら Smalltalk では、ここで望んでいるようなことはできません。SETF はじつはマクロで実現されているから…というのがひとつの答えかもしれませんが、それはいったん脇においやっておくことにして、たとえば元のコレクションから特定した要素を削除するメッセージ(オーソドックスには remove か…)を送信するにしても、その受け手として存在すべき“元のコレクションの○○な要素”というオブジェクトを、first、middle、last といったアクセッサに吐かせるわけにはいかないから…というのがその理由です。(逆に、そうしたオブジェクトを返す専用メソッドを用意しさえすれば実現は容易なわけですが、そうなるとお得(?)な SETF 感覚は失われてしまいます)


でも一方で、たとえば middle の送信により SequenceableCollection >> #middle を起動した際、そのコンテキスト上では self size // 2 + 1 に位置する要素を取り出していることは分かっているわけなので、これを利用する手段がないというのもなんとなくもったいないかんじがするのです。w