RubyでJavaScriptのTemplate literalsの真似をする

この記事は[Okinawa.rb Advent Calendar 2018](https://qiita.com/advent-calendar/2018/okinawarb)の13日目の記事です。
昨日は @hanachin_ さんの[Turnipで書いたfeatureにパスに応じて異なるtypeをつける方法](https://qiita.com/hanachin_/items/e3ad66bbff4da97efafd)でした。
明日は @hanachin_ さんの[配列にためる手間なしenum_for](https://qiita.com/hanachin_/items/134000befc7f5fdce94c)です。

## 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


## つかいかた

インストール方法です

```console
% gem i javascript_template_literals
```

requireしてusingすると使えます。べんりですね。

```rb
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で上書きすると好き勝手な動作ができるので、ここに実装を書いていきます。

```rb
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](https://github.com/banister/binding_of_caller)を使い呼び出し元のBinding下で変数をevalします。

```rb
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します。

```rb
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メソッドに渡す前に展開しようと考えていました。

```rb
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っぽくなくてイケてない。

```rb
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では引数で`()`を使うと配列を分解できます。

```rb
def foo((a, b, c))
  a + b + c
end

puts foo([1, 2, 3])
# 6
```

"配列"と書きましたが、配列に変換できれば分解できます。
配列に変換する際は`to_ary`メソッドが呼ばれます。
`to_ary`で配列に変換できるのであれば配列じゃなくても渡せます。

```rb
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メソッドの引数は`()`で受けとるということにします...!

```rb
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メソッドの引数にいい感じに展開される値を返すことができました! イェイ!

```rb
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で上書きし放題でべんりですね :v: