最近仕事でElixir使うようになって覚えた書き方をメモしておきます(version1.5.0)。
(もっと分かってくれば他にも書いていきます)

Elixir本体

構造体のパターンマッチ

def func(%SomeStruct{status: some_status}, arg2, arg3) do
  # 何らかの処理
end

関数のヘッド部で構造体のフィールドを変数に束縛させてそれを関数の内部で利用するときに使ってます。
ガードと組み合わせて処理をフィールドの値によって振り分けるときにも使えます。

def func(%SomeStruct{status: some_status}, arg2, arg3) when some_status in [0, 1, 2] do
  # 何らかの処理
end
def func(%SomeStruct{status: some_status}, _, _) do
  raise("invalid status: #{some_status}")
end

引数の型でパターンマッチもできる(spec書けば不要です)

def func(%SomeStruct{}=arg) do
  # 何らかの処理
end
def func(%OtherStruct{}=arg) do
  # 何らかの処理
end

Caseとパイプ

使いすぎると可読性が下がりますが関数をパイプでCase式に繋げて処理分岐します。
(例が悪くて申し訳ないです)

# arg = [0, 1, 2, 3, 4]
def func(arg) do
  |> Enum.filter(arg, &Integer.is_odd())
  |> Enum.count
  |> case do
    2 -> "何らかの処理"
    _ -> "何らかの処理"
  end
end

当然ifでも出来ます(が使う機会は今のところありません)

Module.Concat

namespaceをつけてモジュールを宣言するときに使います。モジュールを動的に生成するときに使うことが多いです。

contents  = quote do
  def func(), do: "Hello Elixir!"
end

Module.create(Module.concat(Elixir, Hello), contents, Macro.env.location(__ENV__))

Hello.func()
"Hello Elixir"

構造体の更新

以下の書き方でフィールドを更新した構造体を再作成出来ます。

defmodule Hello
  defstruct [:a, :b, :c, :d]
end

val1 = %Hello{a: 1, b: 2}
# フィールドを更新する
val2 = %Hello{val1|a:3, b: 4} 

Mapの場合は元から用意されているupdate/3update/4を使うことも多いです

val = %{a: 1, b: 2}
val2 = Map.update(val, :a, &(&1+1)) 
#=> %{a: 2, b: 3}
val3 = Map.update(val2, :c, 4, &(&1+1))
#=> %{a: 2, b: 3, c:4}

IO.inspectのラベル

デバッグのときに便利です

arg
|> IO.inspect(label: "before func")
|> func()
|> IO.inspect(label: "after func")

文字列分割

文字列操作で一字ずつ分割したい場合。
(あまり文字列操作する機会ないですが)

String.split("abcde", "", trim: true)
["a", "b", "c", "d", "e"] 

時刻偽装

Rubyのtimecopみたいなものを使いたいなと考えていたのですが、ここでJoseさんがMockについて語っているのをみ、てElixirではあまりMockを推奨してなさそうなのでテストの時に時刻ライブラリを差し替える方法を使っています。

defmodule AppName.Support.TimeMock do
  use Agent
  use Timex

  def start_link do
    Agent.start_link(fn -> %{is_m: false, m_val: nil} end, name: __MODULE__)
  end

  def now() do
    state = Agent.get(__MODULE__, fn(state) -> state end)
    if state[:is_m] do
      state[:m_val] |> Timex.from_unix(:microsecond)
    else
      :os.system_time(:microsecond) |> Timex.from_unix(:microsecond)
    end
  end

  def freeze do
    freeze(:os.system_time(:microsecond))
  end

  def freeze(timestamp) do
    Agent.update(__MODULE__, fn(_state) -> %{is_m: true, m_val: timestamp} end)
  end

  def unfreeze do
    Agent.update(__MODULE__, fn(_state) -> %{is_m: false, m_val: nil} end)
  end
end

このようなモジュールをテスト時のみ使用するライブラリとして用意しておいて、
下記のようにテスト実行時にライブラリを差し替えるようにしています。テスト用のconfigファイルに上で作成したモジュールを指定する必要があります。

@time_module Application.get_env(:app_name, :time)[:time_module] || Timex

def func() do
  epoch = @time_module.now |> Timex.to_unix
  # 何らかの処理
end

テスト時には以下のようにして時刻偽装をしています。

describe "get, set" do
  let :user, do: Hello.Fixtures.User.create
  let :basetime, do: TimeMock.now()

  before_all do
    TimeMock.freeze() # 時刻を止める
  end
  after_all do
    TimeMock.unfreeze() # 時刻を元に戻す
  end

  context "time change" do
    it do
      # 1秒前に進める
      Timex.shift(basetime(), seconds: 1) 
      |> DateTime.to_unix(:microsecond) 
      |> TimeMock.freeze      

      # 何らかのテスト...
    end
  end
end

ライブラリ

Mix.taskのaliases

複数のタスクをまとめられます。

defp aliases do
  ["ecto.setup": ["ecto.create", "ecto.migrate"]]
end

Ectoで発行するクエリの確認

どんなSQLになるのか確認したい場合に使っています。

query = from(u in User, where: u.user_id == ^user_id, update: [inc: ref_cnt: 10])
Ecto.Adapters.SQL.to_sql(:update_all, repo, query)
# {"UPDATE `users` AS u0 SET u0.`ref_cnt` = u0.`ref_cnt` + 10 WHERE (u0.`user_id` = ?)", [100, 10]}

感想

Elixirはもともと文法がシンプルでコードが読みやすいところが良いと思ってました。
今回仕事で初めて使うことになって長く触ってるうちに、文法はシンプルながらも便利な記法が用意されていて、かつライブラリもまぁまぁ困らないくらいにはあるので非常に実用的な言語だと感じてます。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.