macro with elixir
@k1complete
about elixir
 elixirはErlang/OTP上に構築された、マクロを始めとするメタ
 プログラミング機能を特徴とする、Ruby風味の文法の言語。

 Erlangを拡張するというよりも、Erlangの隣に別の言語を構築
 して、相互に利用可能にしている。JavaとScalaのような関
 係。

 今回、loop/recurという機能をテーマにelixirの持つ機能の特徴
 を紹介する
about loop/recur

loop/recurは簡単な再帰をその場で記述できるような
special formで、Clojure由来のもの。

無名関数に対する再帰にまつわる問題ををまろやか
に解決しているスマートな機能。

ただし、便利すぎて使い方が難しい面もある。
usage loop/recur
 たとえばfibonacchi数を求めるコード片
              loop y doで、1変数の再帰ループで、初期値はyであることを示している。

  loop y do
        0 -> 0    cond doパターンと同様にパターンマッチで記述。
                  yが0なら0, 1なら1,それ以外のxなら、という形で記述。

        1 -> 1
        x -> recur(x-1) + recur(x-2)
  end               loopの再帰を行う際のパラメータを指定する。




関数を定義する事無く再帰している。so, cool!!!
Impact of deprecate loop/
recur
loop/recurがelixir-0.6.0でdeprecatedとなり、0.7.0ではspecial formか
ら取り除かれることになった。

  遅いから

  special formは可変個の引数を持てたり、オーバライドされなかっ
  たり、特別なスコープを持ったりした特別なものなので、数を減
  らしたい。

  関数定義すればいいじゃん

うわーん
loop/recur, do it yourself!
 loop/recurがない場合、どうするか?

  等価なコードはこんな感じ(fibonacchi数を例にとると)

    f = fn(f, e) -> case e do
        0->0
        1->1
        e -> f.(f, e-1) + f.(f, e-2)
    end end
    f.(f, y)
 自分でloop/recurを実装してみる。そのためのマクロだw
mloop/mrecur
マクロによるloop/recurをmloop/mrecurとする

構造は、mloop x, do: blockとなっている。block中に、パターンマッ
チを書き、再帰の指示をmrecur(param)として示すことになる。

上記mloop x, do: blockの展開コードは、以下のようになってほしい。

 mblock = block中のrecur(arg)をf.(f,arg)に書き換えたものとする。

 f = fn(f, x) do mblock end
 f.(f, x)
homoiconic syntax in elixir
code
 elixirはhomoiconic構文であり、任意のelixirコードはelixirのデータ構造
 で記述可能。これを勝手に”the tuple”と呼ぶ。

  { function, line, [arg1,args2,...] }
     function: 関数を表すアトムか、”the tuple”

     line: 現在のソースコード上の行番号

     arg1, arg2: functionへの引数で、アトムか、”the tuple”で可変個

 elixirの任意のコード片は一つの”the tuple”で表せる。
consider translation rule
 Elixir構文木タプルでのパーツ表現

   変数に格納した関数オブジェクトを呼び出すためには、”.”演算子を適用する
   が、これは、Elixir構文木タプルとしては、{:”.”, Line, [変数]}となる。

   変数自体は、{:変数名, Line, :quoted}という構造になる。

   一方、関数呼び出しは、{:関数名, Line, [args]}となる。

 まとめると mrecur xは

   {:mrecur, Line, [{:x, Line, :quoted}]} これを、以下のようにすればいい

   {{:”.”, Line, [{:mrecur, Line, :quoted}]}, Line, [{:mrecur, Line, :quoted},{:x,
   LIne, :quoted}]}
traversal elixir tree
 行きがけ順に構文木タプルをなぞり、パターンを認識したら、置き換えるようにす
 ればいい。

 def traverse(a, f) do
    r = f.(a) ## <-- パターンを認識して置き換える関数

    cond do
        is_tuple(r) -> list_to_tuple(traverse(tuple_to_list(r), f))
        is_list(r) -> :lists.map(fn(x) -> traverse(x, f) end, r)
        true -> r
    end
 end
build mloop
mb = traverse(block, fn(x) -> case x do
  {:mrecur, line, args} when is_list(args) -> ## パターン

    {{:”.”, line, [{:mrecur, line, :quoted}]},
           line, [{:mrecur, line, :quoted}|args]}
  _ -> x
  end end)
build mloop 2
defmacro mloop(p, block) do
   mb = traverse(block,...) # ブロックをトラバースしてパターン置き換え

   quote do
      mrecur = fn(mrecur, x) -> # mrecurという名前の変数で無名関数を作成

         case(x, unquote(mb)) # 置き換えたblock

      end
      mrecur.(mrecur, unquote(p)) # loop pのp

   end
end
macro utility
 Macro.expand(quote(do: ....), __ENV__)
   コード片にマクロを一段階だけ適用したコード片
   (“The tuple”)を返す。__ENV__は解釈する環境で通常
   はこのまま。

 Macro.to_binary({“The tuple”})
   “The tuple”形式のタプルをelixirのソース形式の文字
   列へ変換したものを返す。
demo
mloop/mrecur limitation

 loopはloop/n+1というアリティを持つのに対して、
 mloopはmloop/2というアリティを持つ。つまり、変
 数を一つしか持てない。複数欲しい場合はタプルを
 渡すことになる。

 それ以外はloop/recurと同じ機能を持つ, yeah! o/

Elixir macro-in-action-1

  • 1.
  • 2.
    about elixir elixirはErlang/OTP上に構築された、マクロを始めとするメタ プログラミング機能を特徴とする、Ruby風味の文法の言語。 Erlangを拡張するというよりも、Erlangの隣に別の言語を構築 して、相互に利用可能にしている。JavaとScalaのような関 係。 今回、loop/recurという機能をテーマにelixirの持つ機能の特徴 を紹介する
  • 3.
  • 4.
    usage loop/recur たとえばfibonacchi数を求めるコード片 loop y doで、1変数の再帰ループで、初期値はyであることを示している。 loop y do 0 -> 0 cond doパターンと同様にパターンマッチで記述。 yが0なら0, 1なら1,それ以外のxなら、という形で記述。 1 -> 1 x -> recur(x-1) + recur(x-2) end loopの再帰を行う際のパラメータを指定する。 関数を定義する事無く再帰している。so, cool!!!
  • 5.
    Impact of deprecateloop/ recur loop/recurがelixir-0.6.0でdeprecatedとなり、0.7.0ではspecial formか ら取り除かれることになった。 遅いから special formは可変個の引数を持てたり、オーバライドされなかっ たり、特別なスコープを持ったりした特別なものなので、数を減 らしたい。 関数定義すればいいじゃん うわーん
  • 6.
    loop/recur, do ityourself! loop/recurがない場合、どうするか? 等価なコードはこんな感じ(fibonacchi数を例にとると) f = fn(f, e) -> case e do 0->0 1->1 e -> f.(f, e-1) + f.(f, e-2) end end f.(f, y) 自分でloop/recurを実装してみる。そのためのマクロだw
  • 7.
    mloop/mrecur マクロによるloop/recurをmloop/mrecurとする 構造は、mloop x, do:blockとなっている。block中に、パターンマッ チを書き、再帰の指示をmrecur(param)として示すことになる。 上記mloop x, do: blockの展開コードは、以下のようになってほしい。 mblock = block中のrecur(arg)をf.(f,arg)に書き換えたものとする。 f = fn(f, x) do mblock end f.(f, x)
  • 8.
    homoiconic syntax inelixir code elixirはhomoiconic構文であり、任意のelixirコードはelixirのデータ構造 で記述可能。これを勝手に”the tuple”と呼ぶ。 { function, line, [arg1,args2,...] } function: 関数を表すアトムか、”the tuple” line: 現在のソースコード上の行番号 arg1, arg2: functionへの引数で、アトムか、”the tuple”で可変個 elixirの任意のコード片は一つの”the tuple”で表せる。
  • 9.
    consider translation rule Elixir構文木タプルでのパーツ表現 変数に格納した関数オブジェクトを呼び出すためには、”.”演算子を適用する が、これは、Elixir構文木タプルとしては、{:”.”, Line, [変数]}となる。 変数自体は、{:変数名, Line, :quoted}という構造になる。 一方、関数呼び出しは、{:関数名, Line, [args]}となる。 まとめると mrecur xは {:mrecur, Line, [{:x, Line, :quoted}]} これを、以下のようにすればいい {{:”.”, Line, [{:mrecur, Line, :quoted}]}, Line, [{:mrecur, Line, :quoted},{:x, LIne, :quoted}]}
  • 10.
    traversal elixir tree 行きがけ順に構文木タプルをなぞり、パターンを認識したら、置き換えるようにす ればいい。 def traverse(a, f) do r = f.(a) ## <-- パターンを認識して置き換える関数 cond do is_tuple(r) -> list_to_tuple(traverse(tuple_to_list(r), f)) is_list(r) -> :lists.map(fn(x) -> traverse(x, f) end, r) true -> r end end
  • 11.
    build mloop mb =traverse(block, fn(x) -> case x do {:mrecur, line, args} when is_list(args) -> ## パターン {{:”.”, line, [{:mrecur, line, :quoted}]}, line, [{:mrecur, line, :quoted}|args]} _ -> x end end)
  • 12.
    build mloop 2 defmacromloop(p, block) do mb = traverse(block,...) # ブロックをトラバースしてパターン置き換え quote do mrecur = fn(mrecur, x) -> # mrecurという名前の変数で無名関数を作成 case(x, unquote(mb)) # 置き換えたblock end mrecur.(mrecur, unquote(p)) # loop pのp end end
  • 13.
    macro utility Macro.expand(quote(do:....), __ENV__) コード片にマクロを一段階だけ適用したコード片 (“The tuple”)を返す。__ENV__は解釈する環境で通常 はこのまま。 Macro.to_binary({“The tuple”}) “The tuple”形式のタプルをelixirのソース形式の文字 列へ変換したものを返す。
  • 14.
  • 15.
    mloop/mrecur limitation loopはloop/n+1というアリティを持つのに対して、 mloopはmloop/2というアリティを持つ。つまり、変 数を一つしか持てない。複数欲しい場合はタプルを 渡すことになる。 それ以外はloop/recurと同じ機能を持つ, yeah! o/