8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elixir覚え書き

Last updated at Posted at 2018-04-04

Elixirを少しやってみたので覚え書き

基本

  • 数値:1

  • 浮動小数点数:3.14

  • 文字列:"hoge"

  • 真理値:true or false

  • アトム(シンボル)::atom

  • リスト:[3.14, :hoge, "fuga"]

  • タプル:{3.14, :hoge, "fuga"}

  • キーワードリスト:[foo: "bar", hoge: 300]

    • キーがアトム限定の連想配列のようなもの
    • キーの順序は保持される
    • キーが一意かは保証されない
  • マップ:%{:foo => "bar", "hoge" => 300}

    • キーは何でもいい
    • 順序付けされない
    • 重複キーは新しい値に置き換えられる
  • do ~ endが基本

    • do:~ でワンライナー
  • 関数の括弧も判別できれば省略可

    • func(a)func aも可
  • 特殊な記号

    • _ & .. |> | -> <- ^

#IEx
iexというコマンドでインタラクティブにElixirを試せる。
そればかりかiコマンドやhコマンドでそのオブジェクト(という呼び方が正しいかは知らない)の使い方まで教えてくれる。
試しにi "hoge"とすると"hoge"が文字列であることを教えてくれたり、Stringのヘルプを参照するといいよと教えてくれる。
なのでh Stringとすると、ElixirのStringはUTF-8だよ、とかいろいろ教えてくれる。
iexちゃんマジ天使。
なのでとりあえず別ウィンドウでiexを常に走らせておく。困ったら彼女に聞こう。

Mix

mixという諸々をやってくれる装置がある。
アプリを生成したりパッケージの依存関係を解決したりライブラリをインストールしたり……
Rubyのgemやrakeがまとめて入ってるみたいな感じだろうか(イメージ)
実際にgemっぽい動きをしているのはhexというものらしいが今はいいか。
mix helpでコマンドのヘルプが出せる。
とりあえずmix new app_nameでアプリのディレクトリを作って初期化してくれること、mix run filenameでファイルを実行してくれることはわかった。
実際にnewしてみよう。アプリの名前はとりあえずboardで。

hoge>mix new board
* creating README.md
...
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd board
    mix test

ファイルが出来たのでテストを走らせてみよとのこと。

テスト

board> mix test
Compiling 1 file (.ex)
Generated board app
.

Finished in 0.04 seconds
1 test, 0 failures

1つのテストが走った。テスト駆動開発もバッチリだな!
ま、テストの中身(text/board_text.exs)は1 + 1 == 2というテストだったので通って当然だけど…。

実装

lib/board.exにいろいろ書いてみよう。まずはFizzBuzz。

lib/board.ex
defmodule Board do
  @moduledoc """
  Elixirの練習
  """
  
  def fizzbuzz(n) do
    cond do
      rem(n, 15) == 0 -> "FizzBuzz"
      rem(n, 3) == 0 -> "Fizz"
      rem(n, 5) == 0 -> "Buzz"
      true -> to_string(n)
    end
  end

  def upto(max) do
    for i <- 1..max do
      IO.puts(fizzbuzz(i))
    end
  end
end
  • ruby同様、doで始まりendで終わる。
  • 関数はモジュールに属していないといけないらしいので、defmoduleで囲う。
  • condはswitch文みたいなものか。
    • 上から判定していき、trueになったら矢印の右側の値を返す。
    • defaultに相当するものとして、一番下にtrueを書く。
  • for文があり、C#のforeachに似ている。
  • 1..maxはrubyなどにもある範囲(Range)オブジェクト。1..3なら[1,2,3]を一発で表現できる。
  • 標準出力はIO.putsで行う。

実行

コマンドmix run -e "Board.upto(30)"で30までのFizzBuzzを実行できる。
あるいは、iex -S mixとすれば、iexが俺のアプリを読み込んでくれるので、iex上で
Board.upto(30)とすれば結果が返ってくる。iexちゃん天使すぎる。
毎回コマンドを指定していられなくなったら、main.exsとか適当なスクリプトを作り、

main.exs
Board.upto(30)

と保存した後mix run "main.exs"とすれば実行される。

つまり

  • mix run -e "コード"
  • iex -S mixの中でコードを実行
  • mix run "ファイルパス"
    のいずれかで実行できる。

Enum

こういうリスト的なやつはEnumが便利。
Enumを使うとよりきれいに書ける。

def upto(max) do
    Enum.each(1..max, fn i -> IO.puts fizzbuzz(i) end)
end

Enum.eachは列挙(enumerable)と関数(Function)を渡すことで列挙の各要素に関数を適用できる。
C#でいうListのForEachかな。
列挙はさっきの範囲やリストのような続きものの総称。
関数は今回は匿名関数を使っている。
fn 引数 -> 処理 endで書けるぞ。
C#にも似たようなのがあるよね。

list.ForEach(i => Console.WriteLine(i));

みたいな。

ちなみにFunctionは宣言した関数にアンパサンドと引数を付けても得られるので、匿名関数でなく出力する関数を作ってやって

  def print(i) do
    IO.puts fizzbuzz(i)
  end

  def upto(max) do
    Enum.each(1..max, &print(&1))
  end
  # あるいは
  def upto(max) do
    Enum.each(1..max, &print/1)
  end

なんてしても動く

パイプ演算子

IO.puts(fizzbuzz(i))みたいな結果を次の関数の引数にするようなモノはパイプでつなげよう。
|>という演算子を使うと、左辺を右辺の関数の第1引数にできる。
IO.puts(fizzbuzz(i))i |> fizzbuzz |> IO.putsとなるし、
Enum.each(1..10, &IO.puts(&1))1..10 |> Enum.each(&IO.puts(&1))になる。
で、さっきのfizzbuzzをパイプで表現すると、

  def upto(max) do
    1..max |> Enum.each(fn i -> i |> fizzbuzz |> IO.puts end)
  end

となる。おー、すごい……気がする。
「1~max」を「Enum.eachして」、「fizzbuzzして」、「IO.putsで出力」と処理の流れがわかりやすくなった。

パターンマッチ

関数型と言えばパターンマッチ。
elixirでは「=」がそれに当たる。
=は左辺と右辺の構造と値を比較して、当てはまるようなら左辺の変数を束縛(≒代入)する。
当てはまらなければMatchErrorが出る。

たとえばa = 1なら、左辺は変数(なにかしらの値)、右辺は数値の1なので何事もなくaに1が束縛される。
{1, a} = {1, 2}なら、左辺は「1と何かしらの値」、右辺は「1と2」なので滞りなくaに2が束縛される。
{2, a} = {3, 2}だと、左辺が「2と何かしらの値」となっているのに右辺が「1と2」になっていて、左辺の2と右辺の3がマッチしないのでエラーになる。

あと、変数としてマッチしたいけど値として残したくはないときは_という特殊な変数を使う。
_ = 1とするとマッチはするが _を後から参照できない。

つってもリストの総数わかんねーからマッチできねー!ってときのために|がある。パイプで区切るとリストの途中をスキップできる。
[a, b | t] = [1,2,3,4,5]とすればaに1、bに2、tに[3,4,5]が束縛される。

左辺に比較したい変数を置いたのに束縛されちまうよ!っていうときは^(ピン)演算子を使う。
束縛を防いでくれるぞ。

# MatchError
x = 1
^x = 2

case

パターンマッチを使って条件分岐するのがcaseだ。
最初にマッチした値を返してくれる。
caseでFizzBuzzを書き直してみた。

  def fizzbuzz(n) do
    case {rem(n, 3), rem(n, 5)} do
      {0, 0} -> "FizzBuzz"
      {0, _} -> "Fizz"
      {_, 0} -> "Buzz"
      _ -> to_string(n)
    end
  end

3で割った余りと5で割った余りのペアをcaseにかけ、「両方0なら」「3の余りが0なら」「5の余りが0なら」「それ以外なら」とパターンに分ける。
割り算1回分計算を減らせたね。

ガード

ところでfizzbuzz("a")といった風にすると当然エラーが出る。
数値じゃないのに剰余とかはできないからだ。
でも動的型付けでどうやって型を判定しよう?
そこで使うのがガード。

def 関数 when 条件 do と宣言すれば関数を条件を満たしたときだけ実行できる。

def fizzbuzz(n) when n |> is_integer do

とすればnが整数のときだけ実行される。

参考文献

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?