Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Elixirで遊んでみる(2)

本を先に進んでみよう。

2章はパターンマッチ。未だに慣れない。とりあえず=でマッチ出来る。 左側に変数があると、マッチすると束縛が起きる。 左側の変数の値をマッチに使いたい場合は^を付ける。

iex> a = 1
1
iex> [^a, b] = [2, 3]
** (MatchError) no match of right hand side value: [2, 3]
iex> [a, b] = [2, 3]
[2, 3]
iex> a
2

ふむふむ。それ以外は、割と自然だった。

3章は不変性。まあ、関数型だからネ。

4章はElixirの基礎。まずは、組み込みの型。

整数は整数。浮動小数点数はIEEE754 倍精度。アトムはRubyでいうところのシンボルかな。シンタックスも同じだし。範囲もRubyのRange。 正規表現~r{...}optsRubyの%リテラルと同じように{ }は別の記号でもよい。オプションは見慣れないのがあるな・・・。

iex> Regex.replace ~r/,/, "a, b, c", "|" 
"a| b| c"

~r/..../のように/を使うのはノスタルジーだと書かれている(笑)。

システム型として、PIDとポート、リファレンスがあるらしいが後回しにされてる。

コレクションとしては、タプルとリスト、マップとバイナリ。

タプルは、まあ、タプル。RubyならArray。何を入れてもいい。

リストは、いわゆるconsリスト。後で詳しく出てくると思う。あと、キーワードリストという特別扱いされる形式がある。

iex(17)> [a, b] = [name: "Taro", age: 5]
[name: "Taro", age: 5]
iex(18)> a
{:name, "Taro"}

Rubyのハッシュの記法みたいだけど、実体はタプルのリスト。関数呼び出しの最後の引数がキーワードリストの場合、角括弧を省略できる。 つまり、キーワード引数はこう実装されていると。

マップはRubyでいうところのハッシュ。記法も似てる。キーがアトムのときに%{ :key => value}%{key: value}と書いていいのも似てる。 取り出す時は、map1[:key]と書いても良いし、map1.keyでもいい。 こっちをキーワード引数に使わなかったのは、キーワードリストはキーの重複が許されるかららしい。

バイナリはビットストリームを扱う。面白い。

日付型。こんな感じ。リテラル表現の仕方が面白い。シジルっていうらしい。

iex> {:ok, d1} = Date.new(2020, 12, 28)
{:ok, ~D[2020-12-28]}
iex> d1
~D[2020-12-28]
iex> d2 = ~D[2020-12-28]               
~D[2020-12-28]
iex> d1 == d2
true
iex> Date.day_of_week(d1)
1
iex> Date.add(d1, 4)
~D[2021-01-01]
iex> inspect d1, structs: false
"%{__struct__: Date, calendar: Calendar.ISO, day: 28, month: 12, year: 2020}"

時刻型。1秒以下の値をどう持つかがちょっと難しい。「マイクロ秒と有効桁のタプルを持つ」と説明されているけど、ようわからん。

iex> {:ok, t1} = Time.new(12, 23, 34)
{:ok, ~T[12:23:34]}
iex> {:ok, t2} = Time.new(12, 23, 34.56)
** (FunctionClauseError) no function clause matching in Time.new/5    
    
    The following arguments were given to Time.new/5:
    
        # 1
        12
    
        # 2
        23
    
        # 3
        34.56
    
        # 4
        {0, 0}
    
        # 5
        Calendar.ISO
    
    Attempted function clauses (showing 2 out of 2):
    
        def new(hour, minute, second, microsecond, calendar) when is_integer(microsecond)
        def new(hour, minute, second, {microsecond, precision}, calendar) when is_integer(hour) and is_integer(minute) and is_integer(second) and is_integer(microsecond) and is_integer(precision)
    
    (elixir 1.11.2) lib/calendar/time.ex:119: Time.new/5

このように秒に小数は与えられない。

これはOKみたい。

iex> {:ok, t2} = Time.new(12, 23, 34, 56)              
{:ok, ~T[12:23:34.000056]}

リテラルとしては、小数の秒を持てる。が、扱いが難しい。

iex> t1 = ~T[12:23:34.0]
~T[12:23:34.0]
iex> t2 = ~T[12:23:34.00]
~T[12:23:34.00]
iex> t1 == t2
false

これが同じにならないのは、有効桁が違うから、らしい。

DateTimeはタイムゾーンを持つ。タイムゾーンを持たないNaiveDateTimeもある。

真偽値は、true, false, nilの3種類。Rubyと同じかな。falseとnil以外の値が真と扱われることが多いのもRubyと同じ。 ちなみにtrueは:trueとおなじもの。

比較演算子===Rubyと違って厳密な同値を取る。つまり、1 == 1.0はtrue, 1 === 1.0はfalse。 ちなみに、Rubyの===は・・・なんて呼ぶんだろう。むしろ、より緩い比較になる。case式で使う目的で、いろんなクラスで良い感じに定義してある。

irb> /hoge/ === "hogefuga"
=> true
irb> (2..4) === 3
=> true

ブール演算子はandとorのペアと、&&と||のペアがある。andとorは左側(= 1つ目の引数)にtrueかfalseが来ることを期待する。&&はfalseとnil以外はtrueと解釈する。つまり、Perlor dieをしたければ||を使う・・・ということなのか?

コレクションにアイテムが含まれるかどうかはin演算子で確かめる。マップはキーと値のタプルが必要。

iex> 1 in [1, 2, 3]
true
iex> {key1: "value1"} in %{key1: "value1", key2: "value2"}
** (SyntaxError) iex:38:2: syntax error before: key1

iex> {:key1, "value1"} in %{key1: "value1", key2: "value2"}
true
iex> {:key2, "value1"} in %{key1: "value1", key2: "value2"}
false

次に、変数のスコープ・・・といっても特に特殊なことはなくて、普通のレキシカルスコープ。

変わった例として、with式がある。Pythonのwith構文みたいにブロックの中から外に漏れて欲しくないリソースを扱える。 withでパターンマッチして、取り出したものは、doブロックの中だけで使える。

iex> a = [1, 2, 3]
[1, 2, 3]
iex> x = with [_, 2, i] = a do
...(42)> i * 3
...(42)> end
9

パターンマッチに失敗すると=では例外が出るが、<-を使うとパターンマッチの右側がそのまま返る。

iex> x = with [_, 1, i] = a do
...(44)> i * 3
...(44)> end
** (MatchError) no match of right hand side value: [1, 2, 3]

iex> x = with [_, 1, i] <- a do
...(44)> i * 3
...(44)> end
[1, 2, 3]

doのショートカットというのもある。Pythonっぽい?

iex> x = with [_, 1, i] <- a, 
...(45)> do: i * 3
[1, 2, 3]
iex> x = with [_, 2, i] <- a do: i * 3
** (CompileError) iex:46: undefined function a/1
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
iex> x = with [_, 2, i] <- a, do: i * 3
9

ちょっと混乱するね