Elixir GUIDES を普通に読む3

Elixir GUIDES を読んだメモ

elixir-lang.jp

6.バイナリ、文字列、文字リスト

UTF-8Unicode

iex(37)> string = "hello"
"hello"
iex(38)> is_binary(string)
true
  • byte_size/1 はバイト数を返し、String.length/1は文字数を返す
iex(39)> string = "おはよう"             
"おはよう"
iex(40)> byte_size(string)
12
iex(41)> String.length(string)
4
iex(42)> ?お
12362
  • String.codepoints で分割できる iex(56)> String.codepoints("おはよう") ["お", "は", "よ", "う"]

バイナリとビット文字列

  • バイナリは<<>> で表す
iex(57)> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex(58)> byte_size(<<0, 1, 2, 3>>)
4
  • 文字列の連結操作は実際にはバイナリの連結操作
  • 文字列の内部的なバイナリ表現を確かめるために<<0>>を連結させるテクニックがある
iex(59)> "hello" <> <<0>>
<<104, 101, 108, 108, 111, 0>>
iex(60)> <<104, 101, 108, 108, 111 :: utf8>>
"hello"

文字リスト

  • 文字リストはコードポイントのリスト
iex(64)> List.first('hello')
104
  • 文字リストは++で連結する

7. キーワードリストとマップ

キーワードリスト

  • Elixir では、タプルのリストの最初のキーが atom である場合、それをキーワードリストという
iex(65)> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex(66)> list == [a: 1, b: 2]
true
  • キーワードリストでもパターンマッチは扱えるが、要素の数と順序が一致する必要がある
iex(67)> list2 = [a: 1, b: 2]        
[a: 1, b: 2]
iex(68)> [a: a] = [a: 1]
[a: 1]
iex(69)> a
1
iex(70)> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
    (stdlib) erl_eval.erl:453: :erl_eval.expr/5
    (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
    (iex) lib/iex/evaluator.ex:27: IEx.Evaluator.init/4
iex(70)> [b: b, a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
    (stdlib) erl_eval.erl:453: :erl_eval.expr/5
    (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
    (iex) lib/iex/evaluator.ex:27: IEx.Evaluator.init/4

マップ

iex(70)> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex(71)> map[:a]
1
iex(72)> map[2]
:b
iex(73)> map[:c]
nil
  • マップは任意の値をキーとして扱うことができる
  • キーは順序づけされない

  • キーワードリストとは違い、パターンマッチを扱いやすい

  • マップはパターン内のキーが存在する場合マッチする
  • 空マップはすべてのマップにマッチする
iex(82)> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex(83)> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex(84)> a
1
iex(85)> 2
2
iex(86)> map[2]
:b
iex(87)> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
    (stdlib) erl_eval.erl:453: :erl_eval.expr/5
    (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
    (iex) lib/iex/evaluator.ex:27: IEx.Evaluator.init/4
  • マップキーへのアクセス、照合、および追加時に変数を使用できる
iex(87)> n = 1
1
iex(88)> map = %{n => :one}
%{1 => :one}
iex(89)> map[n]
:one
iex(90)> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
%{1 => :one, 2 => :two, 3 => :three}
  • Map モジュールにはマップを操作するための便利な関数がある
iex(91)> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex(92)> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex(93)> Map.to_list(%{:a => 1, 2 => :b}) 
[{2, :b}, {:a, 1}]
  • キーの値を更新するための次のような構文がある
iex(94)> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex(95)> %{map | 2 => "two"}
%{2 => "two", :a => 1}
iex(96)> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
    (stdlib) :maps.update(:c, 3, %{2 => :b, :a => 1})
    (stdlib) erl_eval.erl:256: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1263: :lists.foldl/3
  • マップ内のキーがすべてキーワードの場合、キーワード構文を使用できる

  • また、マップにはアトムキーにアクセスする独自の構文がある

iex(96)> map.a
1

ネストされたデータ構造

  • マップ内マップや、マップ内にキーワードリストを持つデータ構造
iex(97)> users = [
...(97)>   john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
...(97)>   mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
...(97)> ]
[
  john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
  mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
iex(98)> users[:john].age
27
iex(99)> users = put_in users[:john].age, 31
[
  john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
  mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
  • update_in を使えばどのようにマップを更新させるか関数を渡して操作することができる
iex(100)> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[
  john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
  mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}
]
  • get_and_update_in/2などもある詳細は以下

hexdocs.pm