『Programming Elixir』を読んで、写経とかした

読む順番、何か間違えているような気もしますが…
Programming Elixirを読みました。

以下、メモです。
読んでて、まだ知らなかったとか、なるほどなーと感じたところだけ…

nestしたstructにアクセスする便利な機能

iex(1)> defmodule Customer do
...(1)> defstruct name: "", company: ""
...(1)> end
iex(2)> defmodule Bug do
...(2)> defstruct owner: %{}, details: "", s: 1
...(2)> end

iex(3)> report = %Bug{owner: %Customer{}, details: "broke"}
%Bug{details: "broke", owner: %Customer{company: "", name: ""}, s: 1}
iex(4)> report.owner.name
""


# 以下のようにアクセスする
iex(5)> %Bug{ report | owner: %Customer{ report.owner | name: "neko" }}
%Bug{details: "broke", owner: %Customer{company: "", name: "neko"}, s: 1}

# put_inを使うと、より簡単にアクセス可能
iex(6)> put_in report.owner.name, "neko"
%Bug{details: "broke", owner: %Customer{company: "", name: "neko"}, s: 1}

ちなみに、update_in を使うと、

update_in report.owner.name, &("inu" <> "neko")

のように関数を与えることもできる。
さらに、他にもmacroなど用意されてたり、更新する値を [:a, :b, :c] のような記述でkeyを指定できたりと、思いの外奥が深い…

Stream関連

IO.streamの中身(Elixir 1.0.5時点)

  @spec stream(device, :line | pos_integer) :: Enumerable.t
  def stream(device, line_or_codepoints) do
    IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
  end

  # Map the Elixir names for standard io and error to Erlang names
  defp map_dev(:stdio),  do: :standard_io
  defp map_dev(:stderr), do: :standard_error
  defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other

__build__ が何かなーと思うと…

  def __build__(device, raw, line_or_bytes) do
    %IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
  end

なるほどー。何か大層なことしていると思ったのですが、構造体に突っ込んでいるだけなのですね。

StreamはLazy Enumerablesなので、その処理対象が必要になった時に値を処理したい、という時に有効なのですね。

Comprehensions

以下のようにElixirでは記述することができるのですが、この for 、いわゆるループのforだと認識していたのですが、 ふと考えてみると ~ for you とかの、~に対してという感じのforとして使っているのですね。元々そうなのかな。。。そう見えると、この記述、なるほどなーという感じ。

iex> for x <- 1..5, y <- 2..6, rem(x, y) == 0, do: x * y
[4, 9, 8, 16, 25]

Char lists

[a | b] はリストの結合。なので、以下は 'cat' というchar_listに対して、 'dog' という別のリストを結合する、という処理になるので、以下のような形になるのかな。

iex> [ 'cat' | 'dog' ]
['cat', 100, 111, 103]

例えば、以下のような感じ。ここで a のリストがそのまま 'cat' に該当するという。

iex> a = [1,2]
[1, 2]
iex> b = [3,4,5]
[3, 4, 5]
iex> [a | b]
[[1, 2], 3, 4, 5]

escript

以下のように escript を指定することで、mix実行時にコマンドを実行することができる。

defmodule MyApp.Mixfile do
  def project do
    [app: :myapp,
     version: "0.0.1",
     escript: escript]
  end

  def escript do
    [main_module: MyApp.CLI]
  end
end

Cuoncurrent programingの話

processの生成とその経過時間

プロセスに処理を渡すspawnは、 KernelNodeとあって、それぞれがspawn、spawn_link、spawn_monitorなど、種類とその引数のバリエーションがあってパッとこれだ!と思い浮かばない…

http://elixir-lang.org/docs/stable/elixir/Kernel.html#spawn/3

例えば、以下の例はn回プロセスを生成する。その時間を計測する、というもの。

defmodule Chain do
  def counter(next_pid) do
    receive do
      n ->
        send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self,
             fn (_, send_to) ->
               IO.inspect send_to
               spawn Chain, :counter, [send_to]
             end
     send last, 0

     receive do
       final_answer when is_integer(final_answer) ->
         "result is #{inspect final_answer}"
     end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end

名前付け

  • Process.register/2は、Process.alive?/1でtrueとなる、生存しているプロセスに対してPIDと紐づく名前をつけることができる
    • Process.alive?/1が大事で、そこが無いと

番外: Kernel.selfにより得られるPIDの違い

iex > current = self
iex > child   = spawn(fn -> send current, {Kernel.self, 1 + 2} end)
#PID<0.87.0>
iex > flush
{#PID<0.87.0>, 3}
:ok
iex > child   = spawn(fn -> send Kernel.self, {Kernel.self, 1 + 2} end)
#PID<0.87.0>
iex > flush
:ok

これ、少し考えれば面白い。

spawn は、与えた関数を処理する別processを立ち上げ、処理させる。
そのため、その関数内で実行される Kernel.self は立ち上げられた別process内で実行され、そのprocessに関する値を返す。なので、以下の Kernel.self の実行でも異なる結果が表示される。

iex > child   = spawn(fn -> Kernel.self end)
#PID<0.74.0>
iex > Kernel.self
#PID<0.59.0>

macros

Never use a macro when you can use a function.

この言葉から始まるのが印象的。

defmodule My do
  defmacro macro(param) do
    IO.inspect param
  end
end

defmodule Test do
  require My

  My.macro :atom       #=> :atom
  My.macro 1           #=> 1
  My.macro 1.0         #=> 1.0
  My.macro [1, 2, 3]   #=> [1, 2, 3]
  My.macro "binaries"  #=> "binaries"
  My.macro { 1, 2 }    #=> {1, 2}
  My.macro do: 1       #=> [do: 1]
  My.macro do          #=> [do: 1]
    1
  end

  My.macro { 1, 2, 3, 4, 5 } #=> {:{}, [line: 20], [1, 2, 3, 4, 5]}

  My.macro do: ( a = 1; a + a ) #=>
  # [do: {:__block__, [],
  #  [{:=, [line: 22], [{:a, [line: 22], nil}, 1]},
  #   {:+, [line: 22], [{:a, [line: 22], nil}, {:a, [line: 22], nil}]}]}]

  My.macro do #=> [do: {:+, [line: 24], [1, 2]}, else: {:+, [line: 26], [3, 4]}]
    1 + 2
  else
    3 + 4
  end
end

quote and unquote

値をinjectするには2通りの方法がある。1つはunquote。

defmodule My do
  defmacro macro(code) do
    quote do
      IO.inspect code
    end
  end
end

の世界では、 quote で囲まれた範囲はregular codeとしてパースされる。そのため、codeはリテラルとして処理されることになり、CompileErrorとなる。

そのため、regular codeではないことを明示するために、 unquote を使う。

defmodule My do
  defmacro macro(code) do
    quote do
      IO.inspect unquote(code)
    end
  end
end

これにより、

defmodule Test do
  require My
  My.macro(IO.puts "hello")
end

のように使った時、”hello” が出力されるようになる。
これは、Elixirが与えられた値をパースしているときに、 unquote にぶつかったらパースを一旦止め、その unquote されている値のパラメータを生成されたコードにそのまま渡す。その後、またパースを再開する。という挙動をするためです。

ifunless をmacroで書くと以下。unlessは、ifに対して反対の判定結果を与えることで実現が可能。

defmodule My do
  defmacro if(condition, clauses) do
    do_clause = Keyword.get(clauses, :do, nil)
    else_clause = Keyword.get(clauses, :else, nil)

    quote do
      case unquote condition do
        val when val in [false, nil] -> unquote else_clause
        _otherwise -> unquote do_clause
      end
    end
  end

  defmacro unless(condition, clauses) do
    quote do
      if(!unquote(condition), unquote(clauses))
    end
  end
end
quoteとbind_quote

通常、 unquote を使って変数の代入などするのですが、 :bind_quote オプションを付与することで、そのquote内では束縛された変数はすべてunquoteなものとして(明示していないけれども)扱うことが可能になる。

:bind_quoted can be used in many cases and is seen as good practice, not only because it helps us from running into common mistakes but also because it allows us to leverage other tools exposed by macros, such as unquote fragments discussed in some sections below.

defmodule My do
  defmacro mydef(name) do
    quote bind_quoted: [name: name] do
      def unquote(name)(), do: unquote(name)
    end
  end

  defmacro squared(x) do
    quote bind_quoted: [x: x] do
      x * x
    end
  end

end

# invoke without bind_quote
# ** (CompileError) eg.exs:11: invalid syntax in def xA()
#     (elixir) src/elixir_def.erl:44: :elixir_def.store_definition/6
#     (elixir) lib/enum.ex:537: Enum."-each/2-lists^foreach/1-0-"/2
#     (elixir) lib/enum.ex:537: Enum.each/2
#     eg.exs:9: (file)

# invoke without bind_quote
# ** (CompileError) eg.exs:28: function x/0 undefined
#     (stdlib) lists.erl:1337: :lists.foreach/2
#     eg.exs:23: (file)
#     (elixir) lib/code.ex:307: Code.require_file/2

なるほど。少し頭にすんなり入りませんでしたが、理解できた気がします。

Invoke quoted codes
iex(1)> frag = quote do: IO.puts "hello"
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], ["hello"]}
iex(2)> Code.eval_quoted frag
hello
{:ok, []}
iex(3)>
iex(7)> frag = Code.string_to_quoted("defmodule A do def b(c) do c+1 end end")
{:ok,
 {:defmodule, [line: 1],
  [{:__aliases__, [counter: 0, line: 1], [:A]},
   [do: {:def, [line: 1],
     [{:b, [line: 1], [{:c, [line: 1], nil}]},
      [do: {:+, [line: 1], [{:c, [line: 1], nil}, 1]}]]}]]}}
iex(8)> Macro.to_string(frag)
"{:ok, defmodule(A) don  def(b(c)) don    c + 1n  endnend}"
iex(9)> Code.eval_string("[a, a*b, c]", [a: 2, b: 3, c: 4])
{[2, 6, 4], [a: 2, b: 3, c: 4]}

importした以降だけのScopeなのは、まだわかりやすいかな。

defmodule Operators do
  defmacro a + b do
    quote do
      to_string(unquote(a)) <> to_string(unquote(b))
    end
  end
end

defmodule Test do
  IO.puts 123 + 456             #=> "579"
  import Kernel, except: [+: 2]
  import Operators
  IO.puts 123 + 456             #=> "123456"
end

IO.puts 123 + 456               #=> "579"

Macroで定義されている操作には、以下があるとのこと。

iex(1)> require Macro
iex(2)> Macro.binary_ops
[:===, :!==, :==, :!=, :=, :&&, :||, :, :++, :--, :\\, :::, :, :=~, :, :->, :+, :-, :*, :/, :=, :|, :., :and, :or, :when, :in, :~>>,
 :<, :<~, :, :, :<<>>, :|||, :&&&, :^^^, :~~~]

iex(3)> Macro.unary_ops
[:!, :@, :^, :not, :+, :-, :~~~, :&]

Elixirでは、モジュールの振る舞いを、macroの __using__ を使って簡単にinjectできる。

defmodule Tracer do
  defmacro def(definition, do: content) do
    quote do
      Kernel.def(unquote(definition)) do
        IO.puts "==> call: "
        result = unquote(content)
        IO.puts "<==== result: #{result}"
        result
      end
    end
  end

  defmacro __using__(_ops) do
    quote do
      import Kernel, except: [def: 2]
      import Tracer, only: [def: 2]
    end
  end
end

defmodule Test do
  use Tracer
  def neko(a), do: IO.puts a
end


Test.neko 5 #=>
# ==> call:
# 5
# <==== result: ok

このような感じで、対象となるモジュールを use なりで読み込んだら、その __using__ が読み込まれ、ここでは Test モジュールに対して Tracer__using__ でinjectされた内容が常に反映されるようになる。
これにより、ボイラープレートやコードの重複を防ぐことができるようになる。

Protocols

Elixirはクラスが無いので、継承で使うような、親の持つ関数を子が共通して使う、ということができません。その問題に対してprotocolを使って解決しようとしています。Getting Startedにもありましたね。

protocolを使って、複数の defimpl を用意する

# Define defprotocol somewhere.
defprotocol Inspect do
  @fallback_to_any true
  def inspect(things, opts)
end

# Implement protocol for particuler PID
defimpl Inspect, for: PID do
  def inspect(pid, _opts) do
    "my new inspect for pid #PID " <> :erlang.iolist_to_binary(:erlang.pid_to_list(pid))
  end
end

# Be able to set target for multiple types
defimpl Inspect, for: [List, Integer, Float] do
  def inspect(item, _opts) do
    "for any multi types #{item}"
  end
end

defimpl Inspect, for: Atom do
  def inspect(atom, _opts) do
    "my new inspect for atom is " <> :erlang.iolist_to_binary(:erlang.atom_to_list(atom))
  end
end

# my.exs:2: warning: redefining module Inspect
# my.exs:7: warning: redefining module Inspect.PID
# my.exs:13: warning: redefining module Inspect.Atom
#
# iex(1)> inspect self
# "my new inspect for pid #PID<0.70.0>"
# iex(2)> inspect :neko
# "my new inspect for atom is neko"

ここで、 for で指定可能なタイプは以下

  • Any, Atom, BitString, Float, Function, Integer, List, PID, Port, Record, Reference, Tuple

protocolを使って構造体へのAccessを実装する

このprotocolを使って、構造体を持つモジュールへのアクセスを実装すると、親から継承した構造体を使うような感じでモジュールの構造体を利用することもできる。

defmodule Sample do
  defstruct value: 0

  # built-in protocol: http://elixir-lang.org/docs/stable/elixir/Access.html
  # If implement access to %Sample{}, then the Sample module looks as a inherited struct like object oriented language
  defimpl Access do
    def get(container, key) do
      # impliment something
    end

    def get_and_update(container, key, fun) do
      # impliment something
    end
  end

  # built-in protocol: http://elixir-lang.org/docs/stable/elixir/Enumerable.html
  defimpl Enumerable do
    def count(collection) do
      # impliment something
    end

    def member?(collection, value) do
      # impliment something
    end

    def reduce(collection, acc, fun) do
      # impliment something
    end
  end

  # Others...
  # inspect, String.Chars and so on...
end

dialyzer

静的解析ツールとしての dialyzer

dialyzer –build_plt –apps erts kernel stdlib mnesia

try/catch/raise/rescueによるエラーハンドリング

Elixir/Erlangに対して、適切にエラーを処理したいときが多々有ります。そんなとき、以下のようにtry/catch/raise/rescueを使って処理を分けます。

defmodule Sample do
  def neko(n) do
    try do
      raise_error(n)
    rescue
      # error処理
    after
      # 後処理
    end
  end


  def inu(n) do
    try do
      # 何かの処理
    catch
      :exit, code -> # exitシグナルを得たときの処理
      :throw, value -> # throwでなんらかの値を得たとき
      wath, value -> # 上記以外
    end
  end
end

ひとまず、何かするにしても最低限必要な書物と知識は追いついたかな、という感じ。

広告

『Programming Elixir』を読んで、写経とかした” への1件のフィードバック

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中