Open7

WEB+DB PRESS vol.131 の Elixir チュートリアル

yukiyuki

Mac なので下記を実行し、

brew install elixir

無事に入ったことを確認。

❯ elixir --version
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Elixir 1.13.4 (compiled with Erlang/OTP 24)

ちなみに、同時に

  • iex: 対話的インタプリタ
  • mix: パッケージマネージャー

もインストールされている。一度にインストールされるのとてもいい感じ。

yukiyuki

2章

  • 無名関数と名前付き関数で呼び出し方が変わる。「f.(10)」とすると f という変数に束縛された無名関数を呼び出せる。「f(10)」とすると f という名前がついた関数を呼び出しできる。
    • あまり呼び出し方を変えている言語を見ないかも?処理系で何か特別に扱われるとかそういった事情があったりするんだろうか。
  • 下記は無名関数の呼び出し。
iex(6)> f = (fn x -> x + 1 end)
#Function<42.3316493/1 in :erl_eval.expr/6>
iex(7)> f.(10)
11
  • 無名関数自体は他の言語と同様に map のようなコンビネータを呼び出す際に利用される。
iex(1)> Enum.map([1, 2, 3], fn x -> x + 1 end)
[2, 3, 4]
  • 無名関数は & を使って短縮記法を利用できる。ただし、いくつか制約があるので注意。
  • 名前付き関数は下記のように書ける。って思って iex でやってみたけどなんかエラーになる。
iex(1)> def sum(x, y) do
...(1)>   x + y
...(1)> end
** (ArgumentError) cannot invoke def/2 outside module
    (elixir 1.13.4) lib/kernel.ex:6111: Kernel.assert_module_scope/3
    (elixir 1.13.4) lib/kernel.ex:4839: Kernel.define/4
    (elixir 1.13.4) expanding macro: Kernel.def/2
    iex:1: (file)
  • なんとなくだけどモジュールという概念が裏にあって、その中じゃないと関数は定義できないよと言っている?一応調べてみた。この StackOverflow の回答に従ってコードを書いてみた例。
iex(1)> defmodule Expand do
...(1)> def sum(x, y) do
...(1)> x + y
...(1)> end
...(1)> end
{:module, Expand,
 <<70, 79, 82, 49, 0, 0, 5, 100, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 159,
   0, 0, 0, 16, 13, 69, 108, 105, 120, 105, 114, 46, 69, 120, 112, 97, 110, 100,
   8, 95, 95, 105, 110, 102, 111, 95, 95, ...>>, {:sum, 2}}
iex(2)> Expand.sum(1, 2)
3
  • 一旦灰色のボックスの時は iex でがんばらないみたいな感じにするとうまくいくのかな?

  • 「アリティ」という語が唐突に登場するが、ある程度関数型プログラミング言語をやったことがないとわからないかも。解説があるとよかったかもしれない。

  • オプション引数がある。

    • 仮引数の横に \\ とつけて値を入れるとオプション引数になる。
    • def sum(x, y \\ 1), do: x + y とかの場合、sum/2 と同時に sum/1 が暗黙的に定義されることになる。なので、sum(1, 2) でも sum(1) でも呼び出し可能になる。
  • モジュール

    • すでに利用してしまったが defmodule でモジュールを定義できる。モジュール名はパスカルケース。
    • なんかあんまりよくわからないが Elixir のモジュールの都合で「MyApp.String」みたいにすることが多いみたい。
    • モジュールが名前空間や複合データを作る際の中心的な役割を果たす?モジュールでアノテーションや定数定義もできるし、構造体もモジュールによって定義されるみたい?
    • モジュール定義の直下に記述されたプログラムはコンパイル時に評価が走る(!)
  • 下記は構造体。

iex(3)> defmodule Model.User do
...(3)>   defstruct id: nil, name: "John"
...(3)> end
{:module, Model.User,
 <<70, 79, 82, 49, 0, 0, 7, 84, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 220,
   0, 0, 0, 20, 17, 69, 108, 105, 120, 105, 114, 46, 77, 111, 100, 101, 108, 46,
   85, 115, 101, 114, 8, 95, 95, 105, 110, ...>>,
 %Model.User{id: nil, name: "John"}}
yukiyuki

3章

  • = 演算子
    • Elixir では「マッチ演算子」という名前になる。ただ内実は変数を束縛している。
    • a = 1a1 という値を束縛する。
  • ^ 演算子
    • 一度束縛した値の前に ^ をつけると再束縛できなくできる。
    • ただし、もう一度普通の束縛に戻すと再束縛できる。
iex(9)> a = 1
1
iex(10)> ^a = 2
** (MatchError) no match of right hand side value: 2

iex(10)> a = 3
3
  • あんまりまだ使い所がわからないけど、普段からたとえば a = 1 で束縛した後は ^a として構造体とかに渡していく、みたいなコーディングをするということなのかな?
    • わりと使い方謎かも。
  • 関数にガード節をつけてパターンマッチしつつ値を取り出せる。
iex(11)> defmodule WebDB.Guard do
...(11)> def type(a) when is_atom(a), do:
...(11)> "#{a} is atom"
...(11)> def type(a) when is_number(a), do:
...(11)> "#{a} is number"
...(11)> end
{:module, WebDB.Guard,
 <<70, 79, 82, 49, 0, 0, 6, 172, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 211,
   0, 0, 0, 20, 18, 69, 108, 105, 120, 105, 114, 46, 87, 101, 98, 68, 66, 46,
   71, 117, 97, 114, 100, 8, 95, 95, 105, ...>>, {:type, 1}}
iex(12)> WebDB.Guard.type(:a)
"a is atom"
iex(13)> WebDB.Guard.type(1) 
"1 is number"
iex(14)> WebDB.Guard.type("a")
** (FunctionClauseError) no function clause matching in WebDB.Guard.type/1    
    
    The following arguments were given to WebDB.Guard.type/1:
    
        # 1
        "a"
    
    iex:12: WebDB.Guard.type/1
  • 文字列のパターンマッチがおもしろかった。便利そう。
iex(14)> a = "lang: Elixir"
"lang: Elixir"
iex(15)> "lang: " <> b = a
"lang: Elixir"
iex(16)> b
yukiyuki

4章

  • if はマクロ(!)
  • false と nil 以外はすべて true として扱われる。
  • unless もマクロ。
  • cond は if を複数並べたみたいなイメージ?
  • case はいわゆるパターンマッチにあたりそう。
    • エラーハンドリングも case でできる模様
  • with マクロは複数パターンをまとめてマッチさせたい時に使える。

下記は Fizz Buzz を cond を使って書いた例

iex(19)> result =
...(19)> cond do                         
...(19)>   rem(x, 15) == 0 -> "Fizz Buzz"
...(19)>   rem(x, 3) == 0 -> "Fizz"      
...(19)>   rem(x, 5) == 0 -> "Buzz"      
...(19)>   true -> x
...(19)> end

下記はガード節を利用しつつ case を使った例

iex(20)> x = 4
4
iex(21)> result = 
...(21)> case x do
...(21)>   n when n in [1, 3, 4, 5] -> "9:00-18:00"
...(21)>   6 -> "10:00-15:00"
...(21)>   n when n in [2, 7] -> "休業日"
...(21)> end
"9:00-18:00"

制御構文の種類が多い。状況に応じて便利な方を選ぶ感じかな。

ちなみに、制御構文がマクロということはもしかして独自の制御構文作りやすかったりするんだろうか?なんでわざわざマクロになっているのかは気になった。処理系の都合だとは思うけれど。

yukiyuki

5章

  • とりあえずパイプ演算子まじ最高。最高。Elixir といえばこれ、みたいなイメージすらあった。
  • Enum (Enumerable) というのが filter や map などを司っているらしい。
    • Enumerable「プロトコル」と出てきたけど、「プロトコル」が初出な気がする。Swift とか Python でみるアイツと同じですか?と思ったら、公式ドキュメントを見よ、とのことでした。
    • 見てみましたがやはり思っていた通りでした→https://elixir-lang.org/getting-started/protocols.html
    • Elixir での多相性はプロトコルを利用するみたい。
  • for 内包表記もある。Python にあるアイツと同じみたいだけど、
    • :into とすると別のコレクションに詰め替えできる。
    • :uniq すると重複を排除できる。
    • パターンマッチでフィルタリングできる。
    • :reduce でまとめられる。
  • Stream は Enum の遅延評価版。
    • これも基本的には他の言語と同じ。

パイプ演算子。

iex(26)> "hello, world" \
...(26)> |> String.upcase() \
...(26)> |> String.split()
["HELLO,", "WORLD"]

なんかあとは早くアプリケーションを書いて覚えたいかも。ということで、6章を最後にやることにする。

言語への印象と感想

ここまでで基本的な言語機能を触らせてもらえた気がするので、言語に対する初感を書いておきたいと思います。

明らかに関数型プログラミング言語だと思いました(認識が違っていると恥ずかしいのですが)。手続型のプログラミングというよりは宣言的なプログラミングを求められるような文法設計になっている気がします。制御構文が言語の扱い上マクロではあるんですが、このマクロが結局値を返すので実質的には式になっていそうです。また、パイプ演算子をはじめとした機構からも宣言的なプログラミングをさせる傾向にある言語だということがわかるかなと思います。

基本的な特徴が全然わかっていないのですが、動的型付き言語なんでしょうか。構造体が結構不思議な感じだなと思って調べてみたところ、構造体は実質キーが事前に決定された Map と等しい、といったことが書いてあり Python とか JavaScript っぽいなあという感想を抱きました。このあたりの動的性は、連載からはちょっとわかりませんでした。もう少し使い慣れたらわかるのでしょうか。

たとえば大量のデータを捌く処理に結構向いていそうな気がします。Erlang/OTP の上に乗っているのでまず絶対並行処理は得意なはずというのと、やはりパイプ演算子による宣言的プログラミングとイミュータビリティを言語側で担保する設計になっているのは大きそうだなと思っています。日頃作っているアプリケーションについて考えてみても、だいたいの処理はリストを何かしら加工して結果を返す、という行為が大半ですから、そうしたアプリケーションに対しては非常に相性のいい言語仕様な気がしました。リストを捌く際に気持ちいい言語としては Scala がありますが、Scala は JVM の上に乗っているのが個人的には難点だったので、同様にリストの加工に強い言語かつランタイムがいい感じの言語として個人的には注目していきたいと思いました。

ツール周りはしっかりした方だという印象を持っています。とりあえず環境構築がモダンな言語らしくとても楽なのと、コンパイルエラーがなんとなく何を言っているかわかったので扱いやすかったです。たとえば関数を対話的インタプリタで直書きした際に「モジュールに含めないと書けないよ」みたいなエラーが出てきましたが、こういうケースで「syntax error」みたいなコンパイルエラーしか出力しない言語は結構あると思うので情報が伝わってきていいなあと思いました。

どちらかというととっつきやすい言語に分類されるのかなと思いました。いわゆるC言語を踏襲した文法とは大きく異なる記法が多いため、そうした文法に慣れたプログラマにとっては記法のキャッチアップは必要にはなりそうです。一方で言語入門のドキュメントを確認した限りだと仕様が意外とコンパクトで、独特な記法になれてしまいさえすればすぐに使いこなせるようになりそうな印象をもっています。

早くアプリケーションが作りたくなってきたので6章に向かいます!

yukiyuki

6章

とりあえずプロジェクト作成楽すぎ。

$ mix new csv_sample
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/csv_sample.ex
* creating test
* creating test/test_helper.exs
* creating test/csv_sample_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd csv_sample
    mix test

Run "mix help" for more commands.

ついでに VSCode のセットアップもする。調べると「ElixirLS」を使うといいよという記事がいくつか出てくる。これかな?入れてみる。

https://marketplace.visualstudio.com/items?itemName=JakeBecker.elixir-ls

ちなみにちょっと使ってみましたが補完やフォーマッターなど必要なものはきっちり動く印象です。

リポジトリ。

https://github.com/yuk1ty/sandbox/tree/master/elixir/csv_sample