Scalaのラムダ式の_ぽい事をRubyでする便利Gemを作ったヨ!

More than 1 year has passed since last update.

Kasen

kasen.png

Kasen(下線)という名前のGemです.
↑は僕が2時間くらいかけて作ったGemのロゴマーク的なやつです.
(両端の曲度に拘りアリ)

Github: https://github.com/gogotanaka/_
Rubygems: https://rubygems.org/gems/kasen

タイピング量が0.2%減る

0.2%て響きが好きなだけで実際はもっとあるかも!?

echo "gem 'kasen'" >> Gemfile; bundle

or

gem install kasen
require 'kasen'

# 以下の2つは等しい
[[1, 2], [3, 4]].map &_[1]
[[1, 2], [3, 4]].map { |ary| ary[1] }

# 以下の2つは等しい
['0', '1', '2'].select &_.to_i.zero?
['0', '1', '2'].select { |s| s.to_i.zero? }

# 以下の2つは等しい
[['1', '2'], ['3', '4']].map &_.select(&_.to_i.eql?(1))
[['1', '2'], ['3', '4']].map { |ary| ary.select { |n| n.to_i.eql?(1) } }

# 以下の2つは等しい
[1, 2, 3].map &_ + 1
[1, 2, 3].map { |n| n + 1 } 

上の4つの例から雰囲気は掴んで頂けたと思うのですが、bodyがメソッドチェーンからなる1引数のブロックを_をチェーンにして楽々生成してます.

{ |x| x.method1('arg1').method2('arg2-1', 'arg2-2') }

一般に引数が1つでbody内でその引数に対してメソッドをチェーンするようなBlockは

&_.method1('arg1').method2('arg2-1', 'arg2-2')

に書き換える事が出来ます.

note: _が他の役割を果たす場合にはエイリアスであるkを使ってください!
irb_は潰したのでチョロっと何かいじる時に力を発揮すると思います.)

[[1, 2], [3, 4]].map &k[1]

(ちょっとカッコ悪い...)

見た目がちょっとキモかったらパーレンを付けるとイイ感じに!!!

[1, 2, 3].map &(_ + 1)
#=> [2, 3, 4]

[1,2,3].map &:to_s のメソッドチェーン、引数取る版程度に考えて頂ければなと.

意味論的にもより好ましい

pure Rubyでも

[1, 2, 3].map { |n| n.to_s }

よりも

[1, 2, 3].map &:to_s

が好まれるようにブロックを無名関数的に使う際には自明な引数や変数は省略する方がその意味論からしても好ましい.

といった種の文脈は

メソッドチェーンや

[1, 2, 3].map { |n| n.to_s.length }

引数を取るメソッド

[1, 2, 3].map { |n| n + 1 }

にも適応されるべきで、

[1, 2, 3].map &_.to_s.length
[1, 2, 3].map &_.+(1)

みたいにかけるべきですよね、といった話は至って自然に思われる.

_ について

_ は思わぬところで予約されているので注意が必要です.

そもそもそのスコープで変数として存在していると非常に面倒です.(なんとかしたい)

エイリアスであるKernel#k を使ってもいいですが見た目的に_がいいですよね.

めちゃクリーン

この手のハック系のやつはなんとなく自身の大事なコードに持ち込むのがためらわれますが、

50行ほどのコードですので、こんなGemをインストールせずとも、ご自身で読み解いて忍ばせてもOKですb

仕組みちゃん

仕組みを簡単に説明します.

すべての組み込みメソッドを取り除いたEmptyObjectクラスを作ります.

class EmptyObject
  # 警告を潰す
  verbose, $VERBOSE = $VERBOSE, nil
  begin
    instance_methods(true).each { |meth| undef_method(meth) }
    private_instance_methods(true).each { |meth| undef_method(meth) }
  ensure
    $VERBOSE = verbose
  end
end

これでEmptyObject クラスのオブジェクトに対するメソッド呼び出しは全てメソッドmissingでひっかけられるようになりました.

Context クラスをこんな感じで作る.

class Context < EmptyObject
    def initialize
    @__kasen__ = Proc.new { |o| o }
  end

  def to_proc
    @__kasen__
  end

  def method_missing(name, *args, &block)
    __kasen__ = @__kasen__
    @__kasen__ = Proc.new { |o| (__kasen__.(o)).send(name, *args, &block) }
    self
  end
end

全くの空であるKasen::Contextクラスのオブジェクトに対してメソッドが呼ばれる度に、そのコンテキストを再帰的にProcに詰めて最後ひとつのProcにして取り出すという感じです.

あとはKernel#_Kasen::Context.new を生やすとかそんな感じ!

module Kernel
  def _; Kasen.new; end
end

IRBのように変数_が使われている環境では

class ContextGenerator < EmptyObject
  def initialize
  end

  def method_missing(name, *args, &block)
    Kasen::Context.new.send(name, *args, &block)
  end
end

みたいなContextを発生させるやつを_に渡しておきましょう.