1日1時間、業務と関係なく何か勉強しようと思った。
Elixir にした理由: lily whiteと学ぶElixir入門 というのを偶然見つけたから。そんだけ。
Elixir とは
関数型の言語のひとつ。
えりくさーと読む。
Erlang とは
Elixir は Erlang(あーらん) という言語で実装されているので、その特徴を引き継いでるのらしい。
いちいち読み方がむずい。
Erlang は、もともと通信事業者で使われていたようで、並行処理とか分散処理にすごい向いてる。
この Erlang が Facebook のチャットとか、What's up というLINE的なものにも使われてるくらい、イケてるし実績も積んでる。
その Erlang で実装されたのが Elixir。これは期待!!
・・いやいや、Erlang イケてるなら、Erlang で普通に書いたらいいじゃないか。
構文がむずかったり、ツールとかライブラリとかが少ないから流行ってないんだって。可哀想に。
その点、Elixir はイケてる。
Elixir がイケてるところ
書き方はRubyとちょっと似てる!!これは嬉しいね!
でもオレはRubyほとんどやったことないから別にどうってことない。
MixやHexっていうツールが準備されてる!
Mix は プロジェクトの buildツール. とりあえず初期化したり、依存モジュールのinstall. 起動とかまぁプロジェクト全体に対して色々できるやつ。perlでいうと carton的な?
Hex はElixirのライブラリの管理してるやつ。cpan的な?
まぁ、確かに Elixir のほうがよさそうな雰囲気はある。
もう能書きは飽きたのでinstallしてみる。
install
brew install elixir
完。
はろーわーるど
対話環境があるそうな。
$ iex
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
iex
って、いっつも忘れちゃう。elix とかから始まって欲しい。
じゃあはろーしてみる。
iex(1)> IO.puts("はろーわーるど")
はろーわーるど
:ok
いけた。マルチバイト余裕だった。
IO.puts
が、要は print
。めんどいね。
でも System.out.println
よりはましか。
FizzBuzz
FizzBuzz してみよう。
とりあえず動くもの編
#! /usr/bin/env elixir
defmodule FizzBuzz do
def run(limit) do
print(1, limit)
end
defp print(n, limit) when n == limit do
cond do
rem(n, 15) == 0 -> IO.puts "#{n}: FizzBuzz"
rem(n, 5) == 0 -> IO.puts "#{n}: Fizz"
rem(n, 3) == 0 -> IO.puts "#{n}: Buzz"
true -> nil
end
end
defp print(n, limit) do
print(n, n)
print(n + 1, limit)
end
end
input = 10
FizzBuzz.run(input);
# elixir ./fizzbazz.exs で実行
# ----
# 3: Buzz
# 5: Fizz
# 6: Buzz
# 9: Buzz
# 10: Fizz
どうしてこうなった。普通に for 文でくるくるまわすのに。。
Elixir は for 文ないんだって!ヤバイ。
じゃあどうするかっていうと、再帰(recursion) を使うのか。
↑のコードだと、まず run/1
ってのが呼ばれる。
あ、この method/N
っていうのは method名と引数の数を表してて、Elixir ではこうやって記載するんだって。
run/1
から print/2
が呼ばれるんだけど、2個ある!!
でもかたっぽは条件指定してるんです。n
とlimit
が同じときだけ呼ばれる。
今回は run/1
を 10
で呼んでるので、print/2
が 1, 10
の引数で呼ばれるので、
最初は条件にマッチせず、後者の方の print/2
に入る。そこで前者の方の print/2
を呼んで、
1
の部分をインクリして再帰する。
最後インクリされて 10
まで行ったら、今度は後者の print/2
に入らないので、前者の分だけやって終わり。
ふーー。
謎の頭をつかうわ。
ていうか前者とか後者とかその辺の呼び方がほすぃ。
その他スルーしたところ。
- defmodule は module の宣言。
- def と defp で関数宣言。defp は private なので外から呼べないよ。
-
cond do
は雰囲気でわかると思うので省略。 -
rem n, m
は perl で言うところのn % m
。 - ダブルクォートの中で変数展開するときは、
#{hoge}
としなきゃいけない
そしてこのレベル低いサンプル書く中で既にハマっている私。。。
-
cond do
は最後に必ず通るtrue
用の処理が必要みたい。 -
print/2
の順番を条件が付く方を後に書いてたら、無限ループになった。- 上から順番に条件合致するかチェックしている臭い
こんなとこかな。まぁ動いてよかった。
そしてブロックコメントがないぽくてびびった。
これをもうちょっといい感じにしていこう。
引数もらおうぜ編
input = 10
って固定なのはいけてないから引数でもらおう。
System.argv
ってのでもらえるみたいだ。
http://elixir-lang.org/docs/v1.0/elixir/System.html#argv/0 ってのを見つけた。
でもこれListか。まぁそりゃそうか。
今回は1個あればいいのよね。
Listの頭だけ欲しいんだけどat_first的なやつあるかなー。
http://elixir-lang.org/docs/v1.0/elixir/List.html#first/1
あったわ。
これで安心!ってことで、こんな感じで置き換える。
input = System.argv |> List.first
この |>
っていうのは pipeline という記法。
まぁいうたら、
input = List.first(System.argv)
という意味。
けどpipelineつかっておしゃれに書くのがElixir流。たしかにちょっときもちいー。
おしゃれに書けたし、これでバッチリ!引数に10つけて実行!
・・・なぜだ。なぜか無限ループ。。。
ハマりまくって、ハマりまくって、もう寝ようかなーとおもったタイミングで解決した。
input = System.argv |> List.first |> String.to_integer;
とぅーいんてじゃーー!
iex(15)> 1 == "1"
false
いやーそうかそうか。そういうもんか。
甘えていた自分がいたのね。
いやーいい感じに型変換してくれると思うじゃない。それは甘えだったのか。
いやー。ゆとってた。いやーまじかー。
printf編
30を引数に実行とかすると。
$ elixir ./fizzbuzz.exs 30
3: Buzz
5: Fizz
6: Buzz
9: Buzz
10: Fizz
12: Buzz
15: FizzBuzz
18: Buzz
20: Fizz
21: Buzz
24: Buzz
25: Fizz
27: Buzz
30: FizzBuzz
ちょっとずれるのがキモいから、printf '%02d' $n
的なことがやりたい。
どうやるんだろ。ぐぐる。英語ばっかり。
なんか、Erlang のほうに io:format っていうのがあるらしい。
でもこれ Elixir だし。。。
でも :io.format
って感じで呼ぶことで Erlang の関数呼べるらしい。よかった。
だがそれよりも、このformatがむずすぎる。
だめだ。全然わからん。マニュアルみてもさっぱりわからなかったが、色々なサイト見た結果、
iex(51)> :io.format("~2..0B~n", [1])
01
:ok
って感じでやればいいらしい。 ~n
は改行だから無視してよし。
ということで、IO.puts
してたところを書き換えて
cond do
rem(n, 15) == 0 -> :io.format "~2..0B: FizzBuzz~n", [n]
rem(n, 5) == 0 -> :io.format "~2..0B: Fizz~n", [n]
rem(n, 3) == 0 -> :io.format "~2..0B: Buzz~n", [n]
true -> nil
end
こうすればいい。
03: Buzz
05: Fizz
06: Buzz
09: Buzz
10: Fizz
12: Buzz
.
.
うん。
出力は綺麗になったけど、コードはキモくなった
printf編 リベンジ
いやーやっぱどうにかしたい。あのformatとかキモすぎる。
そこで、 Exprintf というモジュールを使えばいつものprintfが使えるようだ。
でも外部モジュールだね。
外部モジュールを使う場合は mix を使うのが定石のよう。
script 1枚でやってたけど、ちゃんとproject をつくってやってみるとするか。
再掲しておくと、
Mix は プロジェクトの buildツール. とりあえず初期化したり、依存モジュールのinstall. 起動とかまぁプロジェクト全体に対して色々できるやつ。perlでいうと carton的な?
て感じ。
elixir 入れただけで mix はいってるよ。
mix project つくる
$ mix new fizzbuzz
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/fizzbuzz.ex
* creating test
* creating test/test_helper.exs
* creating test/fizzbuzz_test.exs
Your mix project was created successfully.
You can use mix to compile it, test it, and more:
cd fizzbuzz
mix test
Run `mix help` for more commands.
うぇい。
中身はどうかな
$ tree
.
├── README.md
├── _build
│ └── test
│ └── lib
│ └── fizzbuzz
│ └── ebin
│ ├── Elixir.Fizzbuzz.beam
│ └── fizzbuzz.app
├── config
│ └── config.exs
├── lib
│ └── fizzbuzz.ex
├── mix.exs
└── test
├── fizzbuzz_test.exs
└── test_helper.exs
8 directories, 8 files
うぇい。
テスト流してみろって書いてたな。
$ cd fizzbuzz
$ mix test
Compiled lib/fizzbuzz.ex
Generated fizzbuzz app
.
Finished in 0.06 seconds (0.06s on load, 0.00s on tests)
1 tests, 0 failures
うぇい。
テストって何書いてるんだろ。
$ cat test/fizzbuzz_test.exs
defmodule FizzbuzzTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 == 2
end
end
・・・うぇい。
exprintf を入れてみる
まずはcpan的な存在となってるhexにあるかみてみよう。
https://hex.pm/
・・・あった。
では mix.exs ってファイルがあるので、そこに依存を追加する。
node でいうところの package.json 的な存在かな。
defp deps do
[{:exprintf, "~> 0.1.6"}]
end
的な。これ version 書かないといけないのかな。とりあえず最新。って書き方あんのかな。
まぁおいといて 依存をinstallしてみる
$ mix deps
* exprintf (Hex package)
the dependency is not available, run `mix deps.get`
$ mix deps.get
Running dependency resolution
Dependency resolution completed successfully
exprintf: v0.1.6
* Getting exprintf (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/exprintf-0.1.6.tar)
Fetched package
Unpacked package tarball (/Users/kuwa0224/.hex/packages/exprintf-0.1.6.tar)
入ったにおい。
$ ls -l deps/
total 0
drwxr-xr-x 7 kuwa0224 kuwa0224 238 8 29 13:48 exprintf
まずはmix経由でさっきのやつ実行してみる
まずはさっきの script をproject以下において実行してみよう。
$ mix run fizzbuzz.exs 10
==> exprintf
Compiled lib/exprintf.ex
Generated exprintf app
==> fizzbuzz
Compiled lib/fizzbuzz.ex
Generated fizzbuzz app
14:03:30.781 [error] Loading of /Users/kuwa0224/work/elixir/fizzbuzz/_build/dev/lib/fizzbuzz/ebin/Elixir.FizzBuzz.beam failed: :badfile
14:03:30.781 [error] beam/beam_load.c(1250): Error loading module 'Elixir.FizzBuzz':
module name in object code is Elixir.Fizzbuzz
14:03:30.782 [error] Loading of /Users/kuwa0224/work/elixir/fizzbuzz/_build/dev/lib/fizzbuzz/ebin/Elixir.FizzBuzz.beam failed: :badfile
14:03:30.782 [error] beam/beam_load.c(1250): Error loading module 'Elixir.FizzBuzz':
module name in object code is Elixir.Fizzbuzz
03: Buzz
05: Fizz
06: Buzz
09: Buzz
10: Fizz
おーなんか怒られたーー。でも結果でてる。
あーはいはい。
さっき、mix new fizzbuzz
したときに、lib 以下にFizzBuzzができてたのね。
そんで lib でも script のなかでも defmodule Fizzbuzz
してるからダブってエラーになったんだろう。
じゃあ、script の中から lib の方に処理を移してみよう。
こんな感じ。
defmodule FizzBuzz do
def run(limit) do
print(1, limit)
end
defp print(n, limit) when n == limit do
cond do
rem(n, 15) == 0 -> :io.format "~2..0B: FizzBuzz~n", [n]
rem(n, 5) == 0 -> :io.format "~2..0B: Fizz~n", [n]
rem(n, 3) == 0 -> :io.format "~2..0B: Buzz~n", [n]
true -> nil
end
end
defp print(n, limit) do
print(n, n)
print(n + 1, limit)
end
end
#! /usr/bin/env elixir
input = System.argv |> List.first |> String.to_integer;
FizzBuzz.run(input);
これで defmodule FizzBuzz
は1回しかやってないことになったぞ。
じゃあ実行してみる。
$ mix run fizzbuzz.exs 10
Compiled lib/fizzbuzz.ex
Generated fizzbuzz app
03: Buzz
05: Fizz
06: Buzz
09: Buzz
10: Fizz
うぇーい。
exprintf つかってみる
exprintfのReadme見る限り、import したあと普通に使えばいいみたい。
まぁやってみよう。
defmodule FizzBuzz do
import ExPrintf
def run(limit) do
print(1, limit)
end
defp print(n, limit) when n == limit do
cond do
rem(n, 15) == 0 -> printf "%02d: FizzBuzz\n", [n]
rem(n, 5) == 0 -> printf "%02d: Fizz\n", [n]
rem(n, 3) == 0 -> printf "%02d: Buzz\n", [n]
true -> nil
end
end
defp print(n, limit) do
print(n, n)
print(n + 1, limit)
end
end
$ mix run fizzbuzz.exs 10
Compiled lib/fizzbuzz.ex
Generated fizzbuzz app
03: Buzz
05: Fizz
06: Buzz
09: Buzz
10: Fizz
いやーーだいぶすっきりしたーーー!
作者に感謝。
テスト書くか
まぁ module がつくられたわけだし、テストを書いてみるか。
標準出力のテストってどうやんのかなーとおもって調べてたら、こんなのがあるみたい。
http://elixir-lang.org/docs/v1.0/ex_unit/ExUnit.CaptureIO.html
defmodule FizzbuzzTest do
use ExUnit.Case
import ExUnit.CaptureIO
test "fizzbuzz with 10" do
assert capture_io( fn -> FizzBuzz.run(10) end ) == """
03: Fizz
05: Buzz
06: Fizz
09: Fizz
10: Buzz
"""
end
end
テスト流してみよう!
$ mix test
1) test fizzbuzz with 10 (FizzbuzzTest)
test/fizzbuzz_test.exs:6
Assertion with == failed
code: capture_io(fn -> FizzBuzz.run(10) end) == "03: Fizz\n05: Buzz\n06: Fizz\n09: Fizz\n10: Buzz\n"
lhs: "03: Buzz\n05: Fizz\n06: Buzz\n09: Buzz\n10: Fizz\n"
rhs: "03: Fizz\n05: Buzz\n06: Fizz\n09: Fizz\n10: Buzz\n"
stacktrace:
test/fizzbuzz_test.exs:8
Finished in 0.06 seconds (0.05s on load, 0.01s on tests)
1 tests, 1 failures
Randomized with seed 942417
あ、 Fizz と Buzz 逆だったことに今更きづく。。(ガチ)
直した。
$ mix test
Compiled lib/fizzbuzz.ex
Generated fizzbuzz app
.
Finished in 0.05 seconds (0.05s on load, 0.00s on tests)
1 tests, 0 failures
Randomized with seed 117381
まぁいい練習になったね。テスト1ケースだけどまぁいいか。
・・・とかすんなり書いたけどテストだいぶつまってた。
最初 mock とかいうのを使って書こうとしたんだけど、
関数によってmockできたり、できなかったりがあってだいぶ詰まった。。
実はEnum.map使えばもっとすっきりできた
ここで Elixir の FizzBuzz でぐぐってみた。
めっちゃすっきりしてる。それを活かしてみる。
どうしてこうなった。普通に for 文でくるくるまわすのに。。
Elixir は for 文ないんだって!ヤバイ。
じゃあどうするかっていうと、再帰(recursion) を使うのか。
って書いてたけど、色々見てたらどこだったかに、
「再帰再帰っていっても面倒だよね、一応 Enum.map 関数あるから使おうや」
ってあった。
ほほう。どれどれ。
iex(136)> Enum.map( 1..3, fn(x) -> x + 5 end )
[6, 7, 8]
おぉーー。これは perl の map とノリが一緒だ。こんなんあったのかー。
iex(137)> Enum.map( 1..3, fn(x) -> x + 6 end )
'\a\b\t'
え?は?どうしたどうした。
5 を 6 に変えただけで事件が起きたんだけど。
iex(141)> [7, 8, 9] == '\a\b\t'
true
いやでも、 true なんだねー。へーーーー。
よくわからんが内部的には同じものとして扱っているんだね。
シングルクォートの場合は各文字の文字コードのリストになるっていう仕様があるらしいし。
びっくりしたけど、実は同じ実体なんだね。
それにしてもバビるな。これわ。
何ってことない数字のリストが、シングルクォートの文字と同じ実体。
っていうのはElixirの特徴と一つとい言えるんだろう。
いやーこれ結構罠なんじゃない?そんなことないのかな。
というのは置いておいて、map が使えそうなのはわかった。
これを使ってFizzBuzz書きなおしてみよう。
defmodule FizzBuzz do
import ExPrintf
def run(limit) do
Enum.map(1..limit, &(print &1))
end
defp print(n) do
cond do
rem(n, 15) == 0 -> printf "%02d: FizzBuzz\n", [n]
rem(n, 5) == 0 -> printf "%02d: Buzz\n", [n]
rem(n, 3) == 0 -> printf "%02d: Fizz\n", [n]
true -> nil
end
end
end
動きました。めっちゃスッキリ。
map
の第二引数のcoderef 渡すところ調子こいて別な書き方にしてみました。
こういう書き方もありなんだね。
パターンマッチングちゃんとつかってみるか
Elixir はパターンマッチングが強力らしいので、
まぁ若干無理やりだけど使ってみるか。
こんなんになる。
defp print(n) do
case { rem(n, 3), rem(n, 5), n } do
{ 0, 0, n } -> printf "%02d: FizzBuzz\n", [n]
{ _, 0, n } -> printf "%02d: Buzz\n", [n]
{ 0, _, n } -> printf "%02d: Fizz\n", [n]
{ _, _, n } -> nil
end
end
ちょっとすっきりしたかも?
_
は無視していいやつ。 case
の横に。ターゲットとなるものを書いて、
各行でマッチングチェックしていく感じ。
ちなみに一番下の nil
のやつがないと、もしどこにも引っかからないときがあったら、
** (CaseClauseError) no case clause matching: {1, 1, 1}
って怒られる
ていうか並行処理まったくやってなくね
折角なのでこれもFizzBuzzでやってみる。
並行処理は spawn / send / receive をうまく使うみたい。
- spawn で別プロセスにて処理を実行させる。
- send でその別れたプロセスから、別のプロセスへメッセージング。
- receive でメッセージングを受け取る。
うん。まぁまぁわかりますな。
ちなみにこの別プロセスってやつだけども、
ElixirのプロセスをOSのプロセスと混同してはいけません.プロセスは(他の多くのプログラミング言語におけるスレッドとは異なり)メモリとCPUにとって非常に軽量です.ですから,同時に動作する数千のプロセスを保持することは特別珍しいことではありません.
by http://elixir-ja.sena-net.works/getting_started/11.html
とのことでした。まぁとにかく軽いと。
で、これらを使って実装してみるとこうなった。
defmodule FizzBuzz do
import ExPrintf
def run(limit) do
parent = self()
pids = Enum.map(1..limit, fn(n) ->
spawn fn ->
send parent, calculate(n)
end
end)
Enum.map(pids, fn(pid) ->
receive do
{ :true, n, msg } -> printf "%02d: %s\n", [n, msg]
{ :false, _, nil } -> nil
end
end)
end
defp calculate(n) do
case { rem(n, 3), rem(n, 5), n } do
{ 0, 0, n } -> { :true, n, "FizzBuzz" }
{ _, 0, n } -> { :true, n, "Buzz" }
{ 0, _, n } -> { :true, n, "Fizz" }
{ _, _, n } -> { :false, n, nil }
end
end
end
ちゃんとreceive 使いたかったので、さっきの例の print/1
を calcurate/1
に変えました。
まぁ計算だけして、printはしないようになったよ。くらいですな。
まぁ流れとしては、多分むずくないとおもう。
-
self()
で今のpidがとれるんでとっておく。 - そのあと、
Enum.map/2
で クルクル処理を回す
- そこでは
fn -> send parent, caluculate(n) end
って無名関数を各子プロセスでやってくれーいって感じで回している。
- spawnの返り値は、子プロセスのpidになるので、子プロセスのpidの配列が pids に格納される
- で、その後子プロセス配列を map で回し、子プロセスからの通知を receive で待つ。
- そんなことを親プロセスがしている間に、子プロセスは さっき指定された
calucurate/1
を実行している
- この
case
句でかかれたマッチングの行の右辺がそのまま返り値になる - 元々
send parent, caluculate(n)
で呼ばれていたので、これがこのまま 親プロセスのreceive
で受け取られる
- 親プロセスで、receive できたら、それを更にマッチングにかけて
printf
を実行する - 親プロセスはまた次の receive を行う
というざっくり流れ。
意外と普通。
sleep sort
前に先輩にAnyEventの練習でおしえてもらってsleep sortってのがあったのでElixirでもやってみよう。
sleep sort ってのは、数字の配列のsortをsleepを使ってやるというまさかのソートアルゴリズムw
#! /usr/bin/env elixir
arr = Enum.shuffle(1..10)
IO.puts "target is ..."
IO.inspect arr
parent = self()
pids = Enum.map( arr, fn(n) ->
spawn fn ->
:timer.sleep n * 1000
send parent, n
end
end)
IO.puts "result is ..."
result = Enum.map( pids, fn(_) ->
receive do
n -> n
end
end)
IO.inspect result
# -----
# $ mix run sleep_sort.exs
# target is ...
# [3, 7, 2, 8, 6, 10, 4, 9, 1, 5]
# result is ...
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
結構かんたんだった。
- IO.inspect は配列とかをdumpしてくれる君
- :timer.sleep で sleep処理はできるが、ミリ秒単位で待っていることを注意
参考にさせてもらったやつ
- http://learn-with-muse.sato-t.net/
- http://elixir-lang.org/
- http://www.leighhalliday.com/fizzbuzz-in-elixir
- http://erlang.org/doc/index.html
- https://github.com/parroty/exprintf
- http://stackoverflow.com/questions/1251869/how-to-format-a-number-with-padding-in-erlang
- https://gist.github.com/mizchi/d712c1d36fa95177b96c
- http://stackoverflow.com/questions/22431816/is-there-something-like-test-requires-in-elixirs-mix