この記事はOkinawa.rb Advent Calendar 2018の13日目の記事です。
昨日は @hanachin_ さんのTurnipで書いたfeatureにパスに応じて異なるtypeをつける方法でした。
明日は @hanachin_ さんの配列にためる手間なしenum_forです。
javascript_template_literals gem
Rubyの`の動作の変更をしてJavaScriptのTemplate literalsの真似をするgemです。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
ソースコードです。
https://github.com/hanachin/javascript_template_literals
つかいかた
インストール方法です
% gem i javascript_template_literals
requireしてusingすると使えます。べんりですね。
require "javascript_template_literals"
using JavaScriptTemplateLiterals
a = 5
b = 10
puts `Fifteen is ${a + b} and
not ${2 * a + b}.`
# Fifteen is 15 and
# not 20.
def my_tag((strings, person_exp, age_exp))
str0 = strings[0]
str1 = strings[1]
if age_exp > 99
age_str = "centenarian"
else
age_str = "youngster"
end
`${str0}${person_exp}${str1}${age_str}`
end
person = "Mike"
age = 28
puts my_tag`That ${ person } is a ${ age }`
# That Mike is a youngster
実装方法
Rubyの`メソッド
Rubyでは`は、ただのメソッドです。Kernelに生えています。
https://docs.ruby-lang.org/ja/latest/class/Kernel.html#M_--60
通常は外部コマンドを実行します。
Refinementsで上書きすると好き勝手な動作ができるので、ここに実装を書いていきます。
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
# ここでJavaScriptのTemplate literals風の動作をさせる
"bar"
end
end
end
using JavaScriptTemplateLiterals
puts `foo`
# bar
呼び出し元の変数を参照する
JavaScriptのTemplate literalsでは${}
の中から変数を参照できます。
Rubyで`メソッドの中から呼び出し元の変数を参照するにはbinding_of_callerを使い呼び出し元のBinding下で変数をevalします。
require "binding_of_caller"
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
binding.of_caller(1).eval('x')
end
end
end
using JavaScriptTemplateLiterals
x = "foo"
puts `bar`
# foo
式展開で呼び出し元の変数を参照する
正規表現でいい感じに式をとってきて、gsubで置き換える際にbinding_of_callerを使って呼び出し元のBindingでevalします。
require "binding_of_caller"
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
b = binding.of_caller(1)
str.gsub(/\$\{([^\}]*)\}/) { b.eval($1) }
end
end
end
using JavaScriptTemplateLiterals
foo = "bar"
puts `${foo}`
# bar
Tagged templatesのような振る舞いの実装方
最初は`メソッドがtagに渡されるのであれば配列を返して、tagメソッドに渡す前に展開しようと考えていました。
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
# もしここで返した値がtagっぽいメソッドに渡るなら配列を返す
[["foo"], 1, 2]
end
end
end
using JavaScriptTemplateLiterals
def bar(strings, exp1, exp2)
"bar"
end
puts bar(*`${foo}`)
# bar
しかし、tagメソッドにで返した文字列が渡るのか渡らないのか
メソッドの中からは判断できませんでした(うまいやり方をしっている人がいたら教えてください)。
あと*
が必要なのがJavaScriptっぽくなくてイケてない。
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
# ここで返した値がtagっぽいメソッドに渡るかどうか、判断がつかない...
[["foo"], 1, 2]
end
end
end
using JavaScriptTemplateLiterals
def bar(strings, exp1, exp2)
"bar"
end
# これ↓がいけてない
puts bar(*`${foo}`)
# bar
ところで、Rubyでは引数で()
を使うと配列を分解できます。
def foo((a, b, c))
a + b + c
end
puts foo([1, 2, 3])
# 6
"配列"と書きましたが、配列に変換できれば分解できます。
配列に変換する際はto_ary
メソッドが呼ばれます。
to_ary
で配列に変換できるのであれば配列じゃなくても渡せます。
def foo((a, b, c))
a + b + c
end
obj = Object.new
def obj.to_ary
[1, 2, 3]
end
puts foo(obj)
# 6
これを利用して、tagメソッドの引数は()
で受けとるということにします...!
def my_tag((strings), exp1, exp2)
# ...
end
a = 44
b = 23
my_tag`a + b = ${a + b}, a * b = ${a * b}`
`メソッドではdefine_singleton_methodを使い返す文字列にto_aryメソッドを定義します。
これで通常の文字列として利用する場合は通常の文字列として利用でき、tagメソッドに渡すとtagメソッドの引数にいい感じに展開される値を返すことができました! イェイ!
module JavaScriptTemplateLiterals
refine(Object) do
def `(str)
# 普通は文字列として振る舞う
ret = "foo2000bar"
# to_aryメソッドがあるため
# tagメソッドに渡されたときはタグっぽく振る舞う
ret.define_singleton_method(:to_ary) do
[["foo", "bar"], 2000]
end
ret
end
end
end
using JavaScriptTemplateLiterals
def my_tag((strings, exp))
"#{strings.join}#{exp}"
end
puts `foo${1000 * 2}`
# foo2000bar
puts my_tag `foo${1000 * 2}`
# foobar2000
まとめ
- Rubyの`はただのメソッド、Refinementsで上書きできる
- メソッドの引数は
()
で囲うと分解できる - メソッドの引数が
()
で囲われているとき、わたした引数のto_ary
メソッドが呼ばれ配列に変換してから分解される
RubyではなんでもメソッドなのでRefinementsで上書きし放題でべんりですね