/var/log/messages

Feb 20, 2019 - 3 minute read - Comments - programming

Metaprogramming Elixir (9)

Final Step

Identify Areas for Compile-Time Optimizations とのこと。ぐぐる翻訳によれば

実行時に評価される正規表現を生成できますが、コンパイル時の最適化を適用しましょう。 値を補間する場合にのみ文字列連結を実行する必要がある関数定義を生成できます。 これにより、実行時にパフォーマンスが大幅に向上します。 interpolate を定義して実装を完成させましょう。その仕事は、必要に応じて補間を使用して t/3 関数本体のASTを返すことです。

とのこと。以下な実装が例示されています。

  defp interpolate(string) do
    ~r/(?<head>)%{[^}]+}(?<tail>)/
    |> Regex.split(string, on: [:head, :tail])
    |> Enum.reduce "", fn
    <<"%{" <> rest>>, acc ->
    key = String.to_atom(String.rstrip(rest, ?}))
    quote do
      unquote(acc) <> to_string(Dict.fetch!(bindings, unquote(key)))
    end
    segment, acc -> quote do: (unquote(acc) <> unquote(segment))
  end

何だこれ。いちおうコンパイルは通っています。github にコードが上がってるみたいですが

全然違うし。直下の記述を機械翻訳してみます。曰く

16行目から、変換文字列を %{varname} パターンで分割します。 次に、各文字列セグメントを縮小し、%{ で始まる任意のセグメントに一致させます。これは変換変数を表します。 内挿された変数が発生すると、Regex.split を単純な文字列連結 AST に変換します。 Dict.fetch! を使います 提供されたバインディング変数に対して、呼び出し元がすべての補間値を確実に提供するようにします。 通常の文字列セグメントの場合は、単純に累積された AST を連結します。 先ほど見た Macro.to_string トリックを使って私たちの解決策をチェックしましょう:

ちなみに、github な実装は以下です。

  defp interpolate(string) do
    ~r/%{[^}]+}/
    |> Regex.split(string, include_captures: true)
    |> Enum.reduce("", fn
      <<"%{" <> rest>>, acc ->
        key = String.to_atom(String.rstrip(rest, ?}))
        quote do
          unquote(acc) <> to_string(Keyword.fetch!(bindings, unquote(key)))
        end
      segment, acc ->
        quote do
          (unquote(acc) <> unquote(segment))
        end
    end)
  end

上記、以下なことがわかりました。elixir 初心者でスミマセン。

  • 最初に出てくる式は Sigils という名前らしい
  • 引数 string を Sigils なナニで split したものをパイプで次に
  • Enum.reduce で accumulate します
  • 手続きは二つ定義されています
    • «”%{}” <> rest», acc な引数 (文字列の先頭が %{ にマッチする)
    • segment, acc な引数 (上記以外)
  • 戻してる (unquote(acc) <> unquote(segment))<> は concat です

とりあえず

Macro.to_string で云々してみます。

すみません

上の実装でコンパイルしてしまいました。出力が以下。

iex(1)> c "translator_final.exs"
[Translator]
iex(2)> c "i18n.exs"
(
  def(t(locale, path, bindings \\ []))
  [[[def(t("fr", "flash.hello", bindings)) do
    (((("" <> "Salut ") <> to_string(Keyword.fetch!(bindings, :first))) <> " ") <> to_string(Keyword.fetch!(bindings, :last))) <> "!"
  end, def(t("fr", "flash.bye", bindings)) do
    (("" <> "Au revoir, ") <> to_string(Keyword.fetch!(bindings, :name))) <> "!"
  end], [def(t("fr", "users.title", bindings)) do
    "" <> "Utilisateurs"
  end]], [[def(t("en", "flash.hello", bindings)) do
    (((("" <> "Hello ") <> to_string(Keyword.fetch!(bindings, :first))) <> " ") <> to_string(Keyword.fetch!(bindings, :last))) <> "!"
  end, def(t("en", "flash.bye", bindings)) do
    (("" <> "Bye, ") <> to_string(Keyword.fetch!(bindings, :name))) <> "!"
  end], [def(t("en", "users.title", bindings)) do
    "" <> "Users"
  end]]]
  def(t(_locale, _path, _bindings)) do
    {:error, :no_translation}
  end
)
[I18n]
iex(3)> I18n.t("en", "flash.hello", first: "Chris", last: "McCord")
"Hello Chris McCord!"
iex(4)> I18n.t("fr", "flash.hello", first: "Chris", last: "McCord")
"Salut Chris McCord!"
iex(5)> I18n.t("en", "users.title")
"Users"

これ、何と言えば良いのでしょうか。そのまんまと言えばそのまんま、って言い方は乱暴だなぁ。そのまんま、になるようにしてるのですが、これを狙って作るのはなかなかアレ。

別途

戻ってコードレビュしたいです。var! が云々、みたいな記述がテキストにありますし。

Metaprogramming Elixir (8) Metaprogramming Elixir (10)

comments powered by Disqus