Kasen
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
を発生させるやつを_
に渡しておきましょう.