elixirだ 第2回
TRANSCRIPT
前回やったこと> Elixirのインストール > 型 > 制御構文case, if, cond > パターンマッチ > モジュール、関数、ディレクティブ > MixとHex > Mixプロジェクトの作成とテスト
例外> (quote do: me) == me > atoms > numbers > lists > strings > tuples with two elements
quoteなしでASTとして使えるという意味でもある
ASTを観察しよう$ iex
iex(1)> quote do ...(1)> String.length(“hey”) ...(1)> end
iex(2)> {name, _, args} = v(1)
iex(3)> Code.eval_quoted v(1)
unquoteしようiex(1)> ast = quote do: String
iex(2)> quote do ...(2)> (unquote ast).length “hey” ...(2)> end
iex(3)> {name, _, args} = v(2)
iex(4)> Code.eval_quoted v(2)
例
defmacro ukenagasu(ast) do ast end
ukenagasu IO.puts(“hey”)
> 入力をそのまま返すマクロ
> astはIO.puts(“hey”)のAST
ifの定義defmacro if(condition, clauses) do do_cl = K.get(clauses, :do) else_cl = K.get(clauses, :else) quote do case unquote(condition) do x when x in [false, nil] -> unquote(else_cl) _ -> unquote(do_cl) end end end ※ 一部略。KはKeywordのalias
defmacro if(condition, clauses) do do_cl = K.get(clauses, :do) else_cl = K.get(clauses, :else) quote do case unquote(condition) do x when x in [false, nil] -> unquote(else_cl) _ -> unquote(do_cl) end end end
ifの定義
※ 一部略。KはKeywordのalias
clausesはASTでは?
defmacro if(condition, clauses) do do_cl = K.get(clauses, :do) else_cl = K.get(clauses, :else) quote do case unquote(condition) do x when x in [false, nil] -> unquote(else_cl) _ -> unquote(do_cl) end end end
ifの定義
※ 一部略。KはKeywordのalias
リストと2要素タプルは ASTになっても形を保つので Keywordモジュールでイジれる
ifの定義defmacro if(condition, clauses) do do_cl = K.get(clauses, :do) else_cl = K.get(clauses, :else) quote do case unquote(condition) do x when x in [false, nil] -> unquote(else_cl) _ -> unquote(do_cl) end end end ※ 一部略。KはKeywordのalias
ifマクロ展開後if(now == :morning, do: “good morning”, else: “hi”)
↓ case now == :morning do x when x in [false, nil] -> “hi” _ -> “good morning” end
つくっちゃってー>与えられたコードを2回実行するマクロ
>ヒントdefmacro twice(do: content) do quote do unquote(content) unquote(content) end end
テストするよdefmodule MakuroTest do use ExUnit.Case import ExUnit.CaptureIO require Makuro
test “twice” do output = capture_io fn -> Makuro.twice do: IO.puts “age” end assert output == “age\nage\n” end end
require?> “マクロを展開する” > マクロを呼び、ASTを得ること > マクロは展開フェイズで呼ばれる > コンパイルされてないと呼べない > 先にコンパイルしておいて欲しいモジュールを示すためにrequire
例えばこれが1行でdefmodule Makuro do defmacro __using__(_) do quote do import Makuro import Makuro.Yabai import Makuro.Kakkoii import Makuro.TotemoYoi end end end
use Makuro
試してみようiex(1)> quote do: 1 == 2 iex(2)> {ope, _, [lhs, rhs]} = v(1)
iex(3)> quote do ...(3)> String.length(“hi”) == 2 ...(3)> end iex(4)> {ope, _, [lhs, rhs]} = v(3)
比較演算子、右辺、左辺が取得できた
オレオレassertdefmacro tadashii(expr) do {ope, _, [lhs, rhs]} = expr quote do left = Macro.to_string(unquote lhs) right = Macro.to_string(unquote rhs)
IO.puts “ひだり: “ <> left IO.puts “みぎ: “ <> right if unquote(expr) do IO.puts “こいつら #{unquote ope}” else IO.puts “こいつら #{unquote ope} じゃない” end end end
オレオレassertiex(1)> require Makuro
iex(2)> Makuro.tadashii 1 == 1 ひだり: 1 みぎ: 1 こいつら ==
iex(3)> Makuro.tadashii 1 == 2 ひだり: 1 みぎ: 2 こいつら == じゃない
test/test_helper.exsExUnit.start
defmodule TestHelper do defmacro puts_expanded(macro) do macro |> Macro.expand(__CALLER__) |> Macro.to_string |> IO.puts end end
test/makuro_test.exs
defmodule MakuroTest do import TestHelper
test “see expanded” do (String.length(“hey”) == 3) |> Makuro.tadashii |> puts_expanded end end
どういうこと?> コンパイル時にElixirコードが実行された > def等がマクロということを考えれば当然
> IOモジュールはコンパイル済みだった > brew installしたとき
> なのでMakuroコンパイル時にputsできた
> 他にもEnumとか使えるのでは?
似た関数をループで定義defmodule Makuro do [ {“mon”, “月”}, {“tue”, “火”}, … ] |> Enum.map fn {en, ja} -> def in_japanese(unquote en) do unquote ja end end end
もっと> ファイルの内容に基づいて関数を定義 > StringモジュールとUnicode > https://hex.pm/packages/colorful
やってみよ>名簿ファイルを読んで、Person構造体を返す関数群を定義しよう
> Person.alex/0 > %Person{name: "alex", age: 20}
alex 20 jack 18 mary 22
priv/people.txt
ヒント> File.read/1 > String.split/2 > String.to_integer/1
> fn_name = String.to_atom(name) > def unquote(fn_name)() do
> (File.stream!)