user
Shun Namiki

Freelance full-stack Endigneer @ Shibuya, Japan

Published on Dec-12th, 2018 ( 10 min read )

【Elixir】約1年ほどElixirを業務で使った自分が最初に知りたかったこと

この記事はQiita - Elixir Advent Calendar 2018 - 12/12の記事です。

はじめに

某スマホゲームのバックエンドで1年くらいElixirで開発して、気付いたら開発リーダーっぽいポジションとかもやっていた。

せっかく1年くらいElixir書いたので、自分がElixirを使うにあたって最初に知りたかったことなどをざっくばらんに書いていく。そのため、この記事は個人の主観によるところが強く、断定口調で書かれているかもしれないのでフィルターを通して読んでほしい。また、Elixirだけでなく言語共有の学ぶ方法や関数型言語の話なども混ざってるのであしからず。

対象読者と期待

  • Elixirこれから学び人 → この中から1つでも持ち帰ってください

  • Elixirちょっと学んだ人 → 補足ください

  • Elixirちょっとデキる人 → マサカリください

Elixirの学習ロードマップを知りたかった

「Elixirの学び方」というよりも「言語の学び方」論の色が強いが「今の自分がElixirを学ぶなら」の学習ロードマップを書いておく。

振り返ると最初はこの3ステップで十分だった。

①基本構文を抑える

②Enumの基本を扱える

③作りたいものを作る/実務を開始する

①基本構文を抑える

ここらへんを中心に読みながらiexで実際に手を動かす。iexはElixirのREPL。

②Enumの基本を扱える

ElixirによるアプリケーションはEnumによるデータ変換がかなりの比率が書かれるケースが多いと思うのでEnum自体を最初のころに学習しておくのは効率的だと思う。もちろん、最初にすべてのEnumを学ぶ必要はないので薄く広くで良い。

Enumの学び方については別記事があるので詳細はこちらを参照。

③作りたいものを作る/実務を開始する

これ以降はElixirを使う理由によって学習ロードマップが大きく変わる。ただ、ガンガン手を動かすのが正義だと思う。

大抵はPhoenixを使ったアプリケーションを作るので思うので何度もmix phx.newしてアプリケーションを作れば作る過程でPhoenixの使い方も覚えていける。

座学で学ぶよりもモノを作りながら覚えるほうが良い。

もし可能なら仕事でElixirを使っているところに早々にジョインするのも良い。

最初はErlangのアクターモデルに手を出さない?

個人的にはElixirの本質はErlangのアクターモデルだと思っている。ただ、もし最初に携わる範囲がAPIサーバだけならむしろ学習の初期はErlangのアクターモデルは出さなくても良かったかもしれない。最初は概念理解の程度にしておいてあとで手を出せば良い。理由は単純に学習範囲が広がってしまうから。

選択と集中の理論に従って範囲を絞る。「Enum・Phoenixの機能を一通り扱えること」のみを中心に進めるとAPIサーバとしてのElixir/Phoenixの使い方なら割と早い段階で扱えるようになる。下地が出来てその後にErlangのアクターモデルやPhoenixのWebSocketやStreamなどを進めるほうが効率的だったと思う。

ただ、Erlangのアクターモデルに関して思想・使い方の学習は早々に進めたい。他の言語/FWを色々触ってきた人なら上記の①②③やFWの使い方は大抵の言語とも似ているので、ある程度わかったら切り上げて、早々にElixirを介したErlangのアクターモデルの使い方などに手をつけても良いと思う。

APIサーバなのかErlangサーバなのか

上記で「APIサーバ」という単語を使っていたが、Elixirでアプリケーションを書くと大抵は下記の2ケースのどちらかになると思う(APIサーバ/Erlangサーバという名称は仮名)

(A) APIサーバ:ユーザからのRequestを元にDBレイヤーからデータ取得しその結果を整形してResponseを行うような一般的なバックエンドのサーバ

(B) Erlangサーバ:ErlangのアクターモデルによるSupervisorTreeを中心にしたErlangサーバ

もちろん簡単に2つに分けられない。アプリケーションによってはこれらを同一のサーバにしていたり、ロジック的にも切れないケースもあるし、そもそも片方の機能しか使わないケースとかもある。

ただ、意識として

「作っているものがどちらなのか?」

「学んでいる/使っている技術はどちらのカテゴリなのか?」

を意識すると、Elixirで提供されている一つ一つの機能・技術がカテゴライズされやすいかと思う。

繰り返すが、上記で書いた通り最初は一般的なAPIサーバとしてのElixir/Phoenixを学んで、慣れてきたら発展した機能にふれると良いと思う。

Elixirらしい書き方を知りたかった

LaravelによるOOPの書き方からElixirに来たのでElixirらしさや関数型らしさなどで、色々戸惑ったところを書いておく。

for構文は使わない

先に断っておくとforが嫌いではないし使う時は使う。が、最初はElixirでは「for使ったら死ぬ」くらいの縛りで始めても良いと思う。自分が個人的にforを使うべきではない理由は

  • 👎拡張性時の可読性が悪い
  • 👎Enumでも表現できる
  • 👎for構文を知らないとわかりにくい

で、逆にforを使う場所は

  • 👍多重ネスト構造
  • 👍絶対に処理が拡張されない場所

だと思っている。ただ、「コードは必ず変更される」の思想で考えるとロジックの部分ではやはりforは使わないほうが良いと思う。

// 👎拡張性時の可読性が悪い
// ①Enum
iex(45)> 1..10 \
...(45)> |> Enum.map(fn x -> x + 3 end) \
...(45)> |> Enum.filter(fn x -> rem(x, 2) == 0 end)
[4, 6, 8, 10, 12]

//for
iex(47)> (for i <- 1..10 do \
...(47)>   i + 3 \
...(47)> end) \
...(47)> |> Enum.filter(fn x -> rem(x, 2) == 0 end)
[4, 6, 8, 10, 12]
// 👎Enumでも表現できる + 👎for構文を知らないとわかりにくい
// ①Enum
iex(42)> 1..5 \
...(42)> |> Enum.filter(fn i -> rem(i,2) == 0 end) \
...(42)> |> Enum.into(%{}, fn i -> {i, i+1000} end)
%{2 => 1002, 4 => 1004}

//for
iex(28)> for i <- [1,2,3,4,5], rem(i,2)==0, into: %{}, do: {i, i+1000}
%{2 => 1002, 4 => 1004}
// 👍多重ネスト構造
// ①Enum
iex(19)> Enum.map(1..3, fn i ->
...(19)>   Enum.map(["a","b","c"], fn x ->
...(19)>     "#{i}_#{x}"
...(19)>   end)
...(19)> end) \
...(19)> |> List.flatten()
["1_a", "1_b", "1_c", "2_a", "2_b", "2_c", "3_a", "3_b", "3_c"]

//for
iex> for i <- [1,2,3], x <- ["a", "b", "c"] do
...>   "#{i}_#{x}"
...> end
["1_a", "1_b", "1_c", "2_a", "2_b", "2_c", "3_a", "3_b", "3_c"]

with構文は使わない

forと近い理由でwithを使うべきでない。こちらも「with使ったら死ぬ」くらいの縛りでちょうど良い。

特にElixirにちょっとこなれてきたタイミングに「with構文ちょっと使ってみるかな」っとなって、めでたくクソコードが生まれのをよく見た。というか自分が生みました、すみません。

withは「caseのネストが発生する場面」以外では使わないで良いという考えだが、それこそElixirチョットデキる人が「そこwithで書くとキレイだよ」と言われたタイミングで初めて取り入れるくらいの気持ちで十分だと思う。

if/case内のブロックで束縛しない

PR指摘でかなりの回数「if/caseの中で束縛しないで!」の指摘をしたので初心者あるあるだと思う。

# 👎バッドパターン
if is_nil(user) do
  user_money = get_money(user)
  user_gacha = get_gacha(user)
else
  user_money = 0
  user_gacha = nil  
end
# 👍グッドパターン
{ user_money, user_gacha } =
  if is_nil(user) do
	{ get_money(user), get_gacha(user) }
  else
  	{ 0, nil }
  end

根底の思想として下記の通りとなる。

  • 「if-do-elseも1つの関数とみなす」
  • 「関数化しつくした最終形態をイメージできること」
  • その上で、「あえて関数化しないという選択肢も持つこと」

詳細はこちらの記事を参照

同一の関数名を定義し、引数で処理を分ける

Laravel作者のTaylorOtwell先生が

「引数に応じて関数の処理を分けるな」

の言葉を元に成長してきた自分だったが、Elixirでは引数で処理をハンドリングしていたので最初はかなり困惑した。

結論、関数型のパラダイムでは下記が正義だと受け入れた。

「 同一の関数名を定義し、引数で処理を分ける」

この世界はそういうもので受け入れば楽だったが「パラダイムが変わる」ことによってむしろ今までの知識が弊害になる、というわかりやすい具体例だったように思う。

ちなみに、「引数に応じて関数の処理を分けるな」は「フラグ引数アンチパターン」というアンチパターンの1つで詳細は下記。

Enumの共通ルールを知りたかった

Enumの関数の命名に関する暗黙ルールを知っておくとEnumについて効率的に学べたので、最初に知りたかった。

サフィックスルール

Enumの関数名に下記のサフィックスがついているケースがある。一部例外らしい動きもあるが、大抵はメインの関数の動きに対して下記の挙動が追加される

  • 〜_by :functionの引数の条件に沿って〜をする
  • 〜_while :条件まで〜の処理を行う(条件にあったら処理が止まる)
  • 〜_every :個数毎に〜の処理を行う

そのため、サフィックスを元に処理が大体推測できるし、記憶もしやすい。

複合Enum関数

Enumには、複数のEnum関数を1つにまとめて提供されている関数がある。例えば、Enum.map_join/3は「Enum.map/2のあとにEnum.join/2を行うEnum関数」だ。学習順序として、それぞれの関数の動きを理解していれば簡単に理解できるので、最初は複合Enum関数の学習や使用は避けて慣れてきてから着手すると良いと思う。

「サフィックスルール」「複合Enum関数」については下記の記事に記載してある。

関数型らしさはHaskellではなくJavaScriptで学ぶべきだった

せっかくElixirを通して関数型を学んでいるので

  • Elixirらしさ

  • 関数型らしさ

を分けて学習するのもよいと思う。「関数型らしさ」を知るおすすめはJavaScriptで学ぶタイプの本だ。

他の本も色々と読んだりしたが大抵の本はHaskellで説明されている。「関数型とは?」を知りたかったはずが気付いたら「Haskellとは?」となりHaskellを勉強していた、ということになりかねない。というか自分がそうなっていた。

JavaScriptで関数型のエッセンスを説明されている本なので「関数型とは?」を知りたければこの本から始めると障壁は低いし、もし深く知りたくなったらその時にHaskellをベースに学び始めてもよいと思う。

ちなみに、関数型を学びたかったはずの自分は最終的にHaskell Day 2018を楽しんでいた。

最後に

全体的にごちゃごちゃとした内容かつポエム色も強い内容になってしまって申し訳ない限りだが、この内容のどれか一つでも誰かの助けになれば幸いです。


snamiki1212Qiita - Elixir Advent Calendar 2018 - 12/12の記事はこれで終わり。

おつかれ、おれ。

お誕生日おめでとう、おれ。

arrow_back

Previous

【まとめ】日本をぶち上げるiNTERFACE SHIFT2018 | エンジニア目線のキャリア戦略の学び

Next

「第二回ボトムアップドメイン駆動設計」に行ってきて軽量DDDを完全に理解した話
arrow_forward