map が面倒なので DelegateMap

簡単なことをする map を書くのが面倒です。配列の各要素に 1 足す程度のことで、いちいち .map {|x| x + 1 } などと 12 ストローク (空白除く) も必要なんですよ。しかもなんかごちゃごちゃして読みにくい。
使用頻度の低い inject とかなら許せるんですが、特に使用頻度の高い map くらいは短く書きたい。ブロック変数名を決める必要があるのも面倒くさい。"{|x|" の部分と "}" の部分が異なる箇所に分かれるのも気に入らない *1
そこで考えた。こんなのどうだろう。

# 各要素に 1 を足す
p [1, 2, 3].dmap + 1          #=> [2, 3, 4]
p [1, 2, 3].map {|x| x + 1 }  # 上と等価

# 二重配列の各要素の先頭の要素を集める (map fst みたいな)
p [[1, 2], [3, 4], [5, 6]].dmap.first  #=> [1, 3, 5]

# なんか変な例
p "foobarbaz".scan(/.../).dmap.sub(/^./) {|s| s.upcase }.join  #=> "FooBarBaz"

Enumerable#dmap が DelegateMap オブジェクトを返す。DelegateMap オブジェクトに対する操作は各要素への操作にすり替わる。実装で言うとこんな感じ。

module Enumerable
  def dmap
    DelegateMap.new(self)
  end
end

class DelegateMap < BasicObject
  def initialize(enum)
    @enum = enum
  end
  def method_missing(mhd, *args, &blk)
    @enum.map {|elem| elem.__send__(mhd, *args, &blk) }
  end
end

半分くらいネタですが半分くらい本気です。method_missing 使ったら負けかなとも思ったけど、delegate の一種だと思えばなんか許せる。許せた。

多重配列の各要素に 1 を足す例はなかなかキモかわいいと思う。

# 普通の配列
p [1].dmap + 1 #=> [2]

# 二重配列
p [[1]].dmap.dmap.dmap + 1 #=> [[2]]

# 三重配列
p [[[1]]].dmap.dmap.dmap.dmap.dmap.dmap.dmap + 1  #=> [[[2]]]

# 四重配列
p [[[[1]]]].dmap.dmap.dmap.dmap.dmap
           .dmap.dmap.dmap.dmap.dmap
           .dmap.dmap.dmap.dmap.dmap + 1  #=> [[[[2]]]]

n 重配列の要素にたどり着くには 2^n - 1 回の dmap が必要なわけです。面白い。いや、なんとかした方がいい気もするけど。


なんか、「プログラミング初心者は配列の各要素への操作と配列への操作を区別しない」とかいうユーザスタディがあって、それを踏まえて配列への操作を各要素への操作に置き換えるプログラミング言語を設計した、とかいう UI 系の論文を大学時代に読んだような記憶があるんですよね。なので意外とありかもしれない。まあその言語名も論文名も思い出せないんだけど。なんだったかなあ。

*1:最近自覚しましたが、ぼくは閉じ括弧が嫌いです。Haskell の $ を考えたやつは天才だと思う。Python のインデントも嫌いじゃない。