🍣

FRICK/Elixir/基本/#01 基本型/演算子/制御構文/パイプ

2022/08/21に公開

初めに

この記事はFRICKのElixir学習のアウトプットである。
Elixirスキルシートに基づいて学習している。
以下のサイトを参考にしている。
https://elixirschool.com/ja/lessons/basics/basics

基本型

1/5 : 整数

iex(1)> 255

2進数、8進数、16進数をサポートしています。

iex(2)> 0b0110
6
iex(3)> 0o644
420
iex(4)> 0x1F
31

2/5 : 浮動小数

Elixrでは、浮動小数点は少なくとも一桁の数字とその後に続く小数を必要とし、64bitの倍制度で、指数eに対応しています。

iex(5)> 3.14
3.14
iex(6)> .14
** (SyntaxError) iex:6:1: syntax error before: '.'
    |
  6 | .14
    | ^
iex(6)> 1.0e-10
1.0e-10

3/5 : 真理値

Elixirは真理値としてtrueとfalseを提供しています。
また、falseとnil以外には真とみなされます。

iex(7)> true
true
iex(8)> false
false
iex(9)> nil
nil

4/5 : 文字列

文字列はElixirではUTF-8エンコードされていて、二重引用符で囲みます。

iex(17)> "Hello"
"Hello"
iex(18)> "dziekuje"
"dziekuje"

文字列は改行やエスケープシーケンスに対応しています。

iex(19)> "foo
...(19)> bar"
"foo\nbar"
iex(20)> "foo\nbar"
"foo\nbar"

5/5 : アトム

アトムは自身の名前がそのまま値になる定数です。
Rubyをご存じなら、シンボルと同義になります。

iex(10)> :foo
:foo
iex(11)> :foo == :bar
false

真理値のtrueとfalseはそれぞれ、アトムの:trueと:falseでもあります。

iex(12)> is_atom(true)
true
iex(13)> is_boolean(:true)
true
iex(14)> true === true
true

Elixirのモジュールの名前もまたアトムです。
MyApp.MyModuleは、そのようなモジュールが宣言されていなくても有効なアトムです。

iex(15)> is_atom(Myapp.MyModule)
true

アトムは、Erlangのビルドインのモノも含めたライブラリのモジュールを
参照するのにもつかわれます。

iex(16)> :crypto.strong_rand_bytes 3
<<195, 167, 238>>

余談

また、Elixirにはより複雑なデータ型も含まれています。
詳しくはコレクションや関数にて。

演算子

1/5 : 算術 (+,-,*,/,dev(),rem())

Elixirの演算子は+,-,*,/を提供しています。
重要な事は、/は常にfloatを返すという事です。

iex(21)> 2 + 2
4
iex(22)> 2 - 1
1
iex(23)> 2 * 5
10
iex(24)> 10 / 5
2.0

整数の除算や除算の余りが必要な場合、便利な関数が2つ用意されています。

iex(25)> div(10,5)
2
iex(26)> rem(10,3)
1

2/5 : ブール演算 (||,&&,!,and,or,not)

Elixirの論理演算子は||,&&,!を提供しています。
どんな方にも対応しています。

iex(27)> -20 || true
-20
iex(28)> false || 42
42
iex(29)> 42 && true
true
iex(30)> 42 && nil
nil
iex(31)> !42
false
iex(32)> !false
true

また、最初の引数が真理値でなければならない3つの演算子and,or,notがあります。

iex(33)> true and 42
42
iex(34)> false or true
true
iex(35)> not false
true
iex(36)> 42 and true
** (BadBooleanError) expected a boolean on left-side of "and", got: 42
iex(36)> not 42
** (ArgumentError) argument error
    :erlang.not(42)

参考:ElixirのandとorはErlangのandalsoとorelseに対応しています。

3/5 : 比較 (==,!=,===,!==,<=,>=,<,>)

Elixirの比較演算子は==,!=,===,!==,<=,>=,<,>です。

iex(36)> 1 > 2
false
iex(37)> 1 != 2
true
iex(38)> 2 == 2
true
iex(39)> 2 <= 3
true

整数と浮動小数を厳密に比べるには===を使います。

iex(40)> 2 == 2.0
true
iex(41)> 2 === 2.0
false

Elixirの重要な特徴はどんな2つの型でも比べられるという事で、ソートに特に有用です。
覚える必要はありませんが、ソートされる順序は以下です。

number < atom < reference < function < port < pid < tuple < map < list < bitstring

これは他の言語では見られないかもしれない、正当で興味深い比較を引き起こします。

iex(42)> :hello > 999
true
iex(43)> {:hello, :world} > {1,2,3}
false

4/5 : 文字列への式展開

文字列の中で#{value}を使う。

iex(44)> name = "FRICK"
"FRICK"
iex(45)> "Hello #{name}"
"Hello FRICK"

5/5 : 文字列の連結

文字列連結は<>演算子を利用します。

iex(46)> "Hello " <> name
"Hello FRICK"

制御構文 (if,unless,case,cond,with)

1/4 : ifとunless

Elixirではifとunlessはほとんど同じように作用しますが、
言語の構成要素としてではなく、マクロとして定義されています。

Elixirでは偽とみなされる値はnilと真理値のfalseだけという事に留意すべきです。

iex(1)> if String.valid?("Hello") do
...(1)>   "Valid String!"
...(1)> else
...(1)>   "Invalid string!"
...(1)> end
"Valid String!"
iex(2)> if "a string value" do
...(2)>   "Truthy"
...(2)> end
"Truthy"

unlessは条件が指定されるときだけ作用する

iex(3)> unless is_integer("hello") do
...(3)>   "Not an Int"
...(3)> end
"Not an Int"

2/4 : cond

条件をマッチさせる必要がある時にcondを使うことが出来ます。
他言語のelse ifのようなものです。

iex(9)> cond do
...(9)> 2 + 2 == 5 -> "this will not be true"
...(9)> 2 * 2 == 3 -> "Nor this"
...(9)> 1 + 1 == 2 -> "but this will"
...(9)> end
"but this will"

マッチしない場合はエラーが起きるので、trueになる条件を定義しましょう。

iex(10)> cond do
...(10)> 7 + 1 == 0 -> "Incorrect"
...(10)> true -> "Catch all"
...(10)> end
"Catch all"

3/4 : case

複数のパターンに対してマッチする必要があるなら、caseを使うことが出来ます。

iex(4)> case {:ok,"Hello World"} do
...(4)> {:ok,result} -> result
...(4)> {:error} -> "Uh oh!"
...(4)> _ -> "Catch all"
...(4)> end
"Hello World"

_変数はcase命令文の中に含まれる重要な要素です。
これが無いとマッチするのもが見当たらない場合エラーが発生します。

iex(5)> case :even do
...(5)>  :odd -> "Odd"
...(5)> end
** (CaseClauseError) no case clause matching: :even

iex(5)> case :even do
...(5)>   :odd -> "Odd"
...(5)>   _ -> "Not Odd"
...(5)> end
"Not Odd"

_は他全てにマッチするelseと考えましょう。

caseはパターンマッチングに依存しているため、
パターンマッチングと同じルールや制限がすべて適用されます。
既存の変数に対してマッチさせようという場合にはピン演算子(^)を使います。

iex(6)> pie = 3.14
3.14
iex(7)> case "cherry pie" do
...(7)> ^pie -> "Not so tasty"
...(7)> pie -> "I bet #{pie} is tasty"
...(7)> end
"I bet cherry pie is tasty"

caseのもうひとつの特徴はガード節に対応している事です。

iex(8)> case {1,2,3} do
...(8)> {1,x,3} when x > 0 -> "will match"
...(8)> _ -> "Won't match"
...(8)> end
"will match"

4/4 : with

特殊形式のWithはネストされたcase文を使うときや綺麗にパイプ出来ない状況に便利です。
With式はキーワード、ジェネレータ、そして式から成り立ちます。

iex(12)> with {:ok,first} <- Map.fetch(user,:first),
...(12)> {:ok,last} <- Map.fetch(user,:last),
...(12)> do: last <> "," <> first
"ELDY,FRICK"

式がマッチに失敗した場合はマッチしない値が返されます。

iex(13)> user = %{first: "doomspork"}
%{first: "doomspork"}
iex(14)> with {:ok,first} <- Map.fetch(user,:first),
...(14)> {:ok,last} <- Map.fetch(user,:last),
...(14)> do: last <> "," <> first
:error

withを使わない長めの例をリファクタリングしましょう。

iex(15)> case Repo.insert(changeset) do
...(15)> {:ok,user} ->
...(15)>   case Guardian.encode_and_sign(user,:token,claims) do
...(15)>   {:ok,jwt,full_claims} -> important_stuff(jwt,full_claims)
...(15)>   error -> error
...(15)> end
...(15)> error -> error
...(15)> end

withを導入するとコードが短く、分かりやすくなります。

iex(15)> with {:ok,user} <- Repo.insert(changeset),
...(15)> {:ok,jwt,full_claims}<-gurdian.encode_and_sign(user,:token,claims),
...(15)> do: important_stuff(jwt,full_claims)

Elixir 1.3からはelseを使うことが出来ます。

import Integer

m = %{a: 1, c: 3}

a =
  with {:ok, number} <- Map.fetch(m, :a),
    true <- is_even(number) do
      IO.puts "#{number} divided by 2 is #{div(number, 2)}"
      :even
  else
    :error ->
      IO.puts("We don't have this item in map")
      :error

    _ ->
      IO.puts("It is odd")
      :odd
  end

caseのようなパターンマッチングを提供することで、エラーを扱いやすくします。
渡されるのはマッチングに失敗した最初の表現式の値です。

パイプライン演算子

パイプライン演算子(|>)はある式の結果を別の死期に一つ目の引数として渡します。
通常の言語

foo(bar(baz(new_function(other_function()))))

Elixirの場合

other_function()
|> new_function()
|> baz()
|> bar()
|> foo()

パイプライン演算子は結果を左に取りそれを右側に渡します。

  • おおまかに文字列をトークン化する
iex(16)> "Elixr rocks" |> String.split()
["Elixr", "rocks"]
  • 全てのトークンを大文字にする
iex(17)> "Elixir rocks" |> String.upcase() |> String.split()
["ELIXIR", "ROCKS"]
  • 文字列の終わりを調べる
iex(18)> "elixir" |> String.ends_with?("ixir")
true

Discussion