Edited at

Elixirで最近覚えた書き方

More than 1 year has passed since last update.

最近仕事で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はもともと文法がシンプルでコードが読みやすいところが良いと思ってました。

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