Help us understand the problem. What is going on with this article?

なぜ僕の中でElixirが一番であり続けるのか

はじめに

僕は去年の4月(2019年)にElixirに出会ってそこからほぼ一年Elixirと苦楽を共にしてきました。

僕のTwitterを見ている方なら、ぼやっと僕のことをElixirの人と認識している人が多いかなって思います(FFの方と会った時の反応などで)
正直Elixirはマイナーな言語ではあるので、そういった意味で印象に残りやすいのだと思います。

この一年、業務+家に帰ってからの趣味プログラミングでもずっとElixirという感じでした。

Elixir以外の言語の勉強にも勤しんでいますが、
Scalaを勉強していても、Pythonを勉強していても、Rubyを勉強していてもやはり僕はElixirが一番好きだと思ってしまう訳です。
そこまでの魅力がElixirにはあります。

今回この記事では僕の思うElixirの持つ魅力(もはや魔力)のさわりを皆さんに紹介します

ツッコミ等あれば優しく(←最重要)コメント欄もしくはTwitterまでお願いします。

一般的に紹介されるElixirの魅力

Elixirには

  • 信頼性の高い"Erlang VM"の元で動作する
  • 並列処理に強い

などの魅力があると一般的に紹介されます。

ほーーん…なるほど…
正直言語によって性能大きく違いが出るほどの大規模なアプリケーションの作成の経験のない僕にはどちらもそこまで実感のあるものではありません

僕が感じたElixirの魅力

では僕がElixirをここまでこよなく愛する理由はなんでしょうか?

  • パイプライン演算子
  • パターンマッチ
  • 関数型

僕はこの三つがElixirの圧倒的魅力・中毒性を引き出していると思います。
主に前半の二つに関して紹介していきます。

まずは「パイプライン演算子」「パターンマッチ」とは何かをかるーく紹介します。

(追記) 公式ではpipe operatorと呼ばれているようですが、一般でもパイプライン演算子と呼ばれることが多いような気がするので、この記事ではパイプライン演算子と統一して呼ぶことにします

パイプライン演算子

"text!" |> IO.puts()  #text!と出力される

上記の|>がパイプライン演算子です。
御察しの通り左の式で評価された内容を右の関数の第一引数として渡すという機能を持ちます。

パターンマッチ

※パターンマッチの説明はElixirの根元に近ずくものでこの記事では深掘りません

map = %{foo: 1, bar:2}
%{foo: hoge} = map
IO.puts(hoge)          #1が出力される

もしかすると「ん?何が起こったの??」と感じる方もいるかもしれないです。
2行目で行われているのがパターンマッチです1
簡単に説明するとmapの内fooのkeyをもつvalueを新たな変数hogeに束縛しています

パターンマッチ辺りを深く知りたい方は以下の記事が役にたつと思います。
[翻訳] Elixirのパターンマッチング

パイプライン演算子とパターンマッチを用いた関数設計

パイプライン演算子により人々(少なくとも僕)はようやくデータフローを意識することができるようになります。

関数というものは基本的にデータを入れたら別のデータが帰ってくるもの(=データの流れ)です。

これを意識しつつ、例として何か一つの関数を書いてみたいと思います。

  • 特定のタグがついている記事を投稿日順で取ってきたい
  • タグには(なぜか)正規IDと省略IDという概念がある

という実際にありそうなget_with_tagsという関数を書きたいとします。
タグの名前とを引数として渡す設定にします2

※ここから説明する関数の設計方法は僕個人の考えなので、他の方みんなこの方法を使っている訳でも、これがベストプラクティス!という訳でもないと思います。

関数の全体像を掴む

まずは大元の流れを掴みます。

post.ex
  def get_with_tags(tag_name, author_name) do
    tag_id = Tag.get_id_by_name(tag_name, :canonical)

    get_posts_query()
    |> filter_by_tag_id(tag_id)
    |> order_by(asc: :created_at)
    |> Repo.all()
  end

この関数定義は大きく二つのセクションに分かれます

tag_id = Tag.get_id_by_name(tag_name, :canonical)

まず上記の部分でメインの処理に必要なものを用意します。
今回は正規のTagのidが必要なのでそれを用意しています。

get_posts_query()
|> filter_by_tag_id(tag_id)
|> order_by(asc: :created_at)
|> Repo.all()                    #ElixirのEcto.Repoモジュールの関数

そして、上記がメインの処理です。
パイプライン演算子により、データを処理をしていく様子が関数名から追うことができるように設計します。

  • posts_queryが
  • tag_idで絞られて
  • created_atの昇順で並べられて
  • Repo.all()される

というデータが加工されていく様子がパッとみてつかめるのでは無いでしょうか

もちろんこの段階ではfilter_by_tag_idなどの関数は実際には定義していません。この段階で処理を関数名だけで読めるようにこのような関数が必要そうだなというものをざっくりパイプライン演算子で並べていきます。

またこの全体像の作成で、意識すべきは
メインの処理以外に長いパイプライン演算子が必要となる場合は別関数として切り出すべき
ということです。

これにより、一つ一つの関数を出来るだけシンプルなものにすることができます。

実際に関数を作成していく

get_with_tagsの設計上で出てきた関数を定義していきます。

以下のorder_byの定義をみてください。

post.ex
  def order_by(asc: params) do
    #paramsを基準に昇順に並び替える
  end

  def order_by(desc: params) do
    #paramsを基準に降順に並び替える
  end

ここではパターンマッチが利用されています。
同じ関数が二度定義されているのは、(key: valueとすると)

  • keyの部分にascが使用された時
  • keyの部分にdescが使用された時

で関数の処理を変えるためです。3 4

先ほどの

長いパイプライン演算子は別関数として切り出すべき

ということを意識すると関数の処理のほとんどを大きなif文で囲うことで条件分岐させるというような必要が出てきてしまいます。

そこにパターンマッチを用いることで、関数内で大きなif文を用いて処理を切り分けるなどの必要がなくなり、関数定義自体の可読性が上がることが分かるかと思います。

そして以下が他の関数の定義も含めたこの関数の全体像になります。
Tagモジュールの方にも先ほど紹介した関数定義にパターンマッチを用いるテクニックが使われています。

post.ex
defmodule Post do
  import Ecto.Query

  alias Ecto.Repo

  def get_with_tags(tag_name, author_name) do
    tag_id = Tag.get_id_by_name(tag_name, :canonical)

    get_posts_query()
    |> filter_by_tag_id(tag_id)
    |> order_by(asc: :created_at)
    |> Repo.all()
  end

  def get_posts_query() do
    from p in Posts
  end

  def filter_by_tag_id(posts, tag_id) do
    from p in ^posts, where p.tag_id == ^tag_id
  end

  def order_by(asc: params) do
    #paramsを基準に昇順に並び替える
  end

  def order_by(desc: params) do
    #paramsを基準に降順に並び替える
  end
end
tag.ex
defmodule Tag do
  def get_id_by_name(name, :cautional) do
    #Tagの名前をもとにTagの正規IDを取得する関数
  end
  def get_id_by_name(name, :summarized) do
    #Tagの名前をもとにTagの省略IDを取得する関数
  end
end

すなわちこの例で何が言いたかったのか

このように設計を行うことで結果として可読性が上がっているような気がしませんか?

  • 関数内のメインとなるパイプライン処理の関数名を見ればどのようなデータの処理が行われているか分かる
  • 小さめに切り分けられた関数定義自体もパターンマッチを用いることで可読性が上がる

これはパイプライン演算子とパターンマッチの両方がないと実現しないものであり、即ちElixir特有の魅力に繋がっていると僕は思うわけです。

終わりに

いかがでしたか??
特にパターンマッチのおかげで美しく設計できるような活用法は他にも沢山あり、この記事だけで到底紹介し切れるものではありません。
この記事でElixirの魅力・魔力が少しでも伝わっていれば嬉しいです。

ちなみに現在Elixirの他にパイプライン演算子を扱えるのはF#やRなどくらいだそうです(安心安全のwikipedia調べ)
最近だとJavaScriptやRubyにもこのパイプライン演算子のようなものを導入しようという動きはあるそうですね。

(番外編)関数型って難しくない?について

※ここで言いたいのは関数型とオブジェクト指向どちらが優れているという話ではありません。

巷で「関数型は難しいよ」と言われることが多いかもしれません。それ、本当ですか??

それは「関数型ってあんまり使ってる人もいないし、参考になるリソースもオブジェクト指向に比べて少ないし難しそうだよね」と履き違えていませんか??(学習の難易度ももちろん"難易度"の一種だとは思いますが)

個人的にはオブジェクト指向の言語で設計する方が考えることも多く、作り手によって完成度に大きな差が出るような気がして、圧倒的に難しい気がします。
オブジェクト指向至高主義のあなたは、まず本当にオブジェクト指向できていますか?

そして肝心のElixirが難しいかどうかに関してですが、

  • ElixirはRubyライクと言われている
  • ElixirのWebフレームワークであるPhoenixはRailsと似ている

2つ目に関してはPhoenixはそもそもRailsのプロジェクトメンバーによって作成されたらしく実際にディレクトリ構成やコマンドなど多くの部分でRailsとPhoenixは似ていると感じることが多いです。
以下の記事を読んでもらうと、「なるほどっ」となるかもしれません
Rails経験者に贈るPhoenix入門


  1. 実際には1行目で行われているものもパターンマッチです。Elixirは変数の代入に見えるものも内部的には全てパターンマッチが利用されています。が、深掘るとかなり細かい話になるので省略します。 

  2. え、そもそもこの関数にidを渡さないような設計になってる時点で怪しいだろって?いい例が思いつかなかったんです… 

  3. 今回のorder_byでは正直関数内にif文などの条件分岐を入れるだけでいいと思いますが、例なのでこれも許してください… 

  4. Elixirが分かる方ならEcto.Query.order_byをわざわざなんでもう一回定義してるんだって感じだと思いますが、例が思いつかなかったのでこれまた許してください… 

sanpo_shiho
京都で大学生してます。
https://linktr.ee/sanpo_shiho
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした