RubyでもElixirの|>
みたいなことをやりたかった。
RubyでShellのようなパイプ演算がしたいという動機は実は無かったのだが、Rubyで定義出来るオペレーターの都合上|
を利用することになり、まぁそれってShellのパイプだよね?ということで本投稿のタイトルは「shellのパイプみたいなことをやる」としている。
こんなコードが書ける
|
オペレーターはArray
やFixnum
で既に定義されているので、影響範囲を局所化するためにrefinementの機能を用いて実装した。
なので、使うためにはusing
キーワードが必要になる。
require 'json'
require 'net/http'
using Pipe
get_url_name = URI('http://qiita.com/k-motoyan.json') \
| Net::HTTP.method(:get) \
| JSON.method(:parse) \
| [:fetch, 'url_name']
get_url_name.call # => 'k-motoyan'
改行したい場合は末尾に\
が必要だとか、引数の必要なProcを呼ぶときは配列にしないといけないとか、処理を実行するときにはcall
を呼ばないといけないとかElixirとかShellと較べてちょっとあれだけど、それっぽいことは実現出来ました。
実装
コードがDryでないとかは黙って見過ごして欲しい。
やっていることは簡単で、
-
|
の左辺に渡されたMethod(シンボルでも良い)をProc化する。 - 1で生成するProcの第一引数に
|
の右辺に渡されたオブジェクトを適用する。 - 2の処理を返すProcを生成する
ということをやっているだけ。
module Pipe
def self.build_proc(proc, apply_proc, *args)
-> { proc.to_proc.call(apply_proc.call, *args) }
end
refine Object do
def |(arg)
if arg.is_a? Array
proc_or_method = arg.shift
Pipe.build_proc proc_or_method, -> { self }, *arg
else
Pipe.build_proc arg, -> { self }
end
end
end
refine Array do
def |(arg)
if arg.is_a? Array
proc_or_method = arg.shift
Pipe.build_proc proc_or_method, -> { self }, *arg
else
Pipe.build_proc arg, -> { self }
end
end
end
refine Fixnum do
def |(arg)
if arg.is_a? Array
proc_or_method = arg.shift
Pipe.build_proc proc_or_method, -> { self }, *arg
else
Pipe.build_proc arg, -> { self }
end
end
end
refine Proc do
def |(arg)
if arg.is_a? Array
proc_or_method = arg.shift
Pipe.build_proc proc_or_method, -> { call }, *arg
else
Pipe.build_proc arg, -> { call }
end
end
end
end
使ってみたいという奇特な人へ
勢い余ってshelike-pipeというGemにしてしまったので、gem install shelike-pipe
とかやれば使えると思う。
ここで書いた内容より、多少高機能になっています。