技術メモ

プログラミングとか電子工作とか

Elixir入門(第二章 無名関数~)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
前回に引き続きElixirのチャプター2です。

無名関数

JavascriptPythonでもおなじみかと思います。変数とかに関数を放り込むやつですね。

iex> add = fn a, b -> a + b end
#Function<12.128620087/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true
iex> is_function(add, 2) # add が 2 つの引数を期待する関数であるのかを確かめる
true
iex> is_function(add, 1) # add が 1 つの引数を期待する関数であるのかを確かめる
false

ここでis_function/1、is_function/2という表記が出てきます。引数の数(アリティ)で関数(の役割)が異なるので前回の「関数の識別」という認識は間違ってなかったと思う。
Elixirでも関数はオブジェクト扱い。無名関数を実行するには少々気持ち悪いが.(引数)と、メソッドみたいな感じ。
たぶん関数あたりの仕様によるものと推測。
他にも色々書いてあるがスコープ云々の話だと思う。関数内部で使われてる変数名は外部には影響しませんよとか。

リスト

iex> [1, 2, true, 3]
[1, 2, true, 3]

いたって普通のリスト(配列)である。length リスト名で長さが取れるみたい。
連結、抽出(削除)が簡単?

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]

--は頭から引かれるっぽい。
また、Elixirのリスト操作はリスト改変ではないので返ってくる値を格納する必要がありそう。
これはどっちか分からなくなり安いのでありがたい仕様。

リストの先頭とそれ以外を取る機能があるそうで。今のところ使いどころが分からない。
先頭に追加したいとき([要素] ++ tl(list))とか?

iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]

ASCIIに該当する数値のリストを使うと文字列っぽく表示してくれる。

iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'

ただし、シングルクォーテーションとダブルクォーテーションでは型が違うみたい。
シングルはリスト(list)、ダブルは文字列(string)。

タプル

Pythonのタプルと同じかと思いきや何かちがうっぽい。

iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2

タプルはPythonの辞書などとは違って(もしかしたらバージョンアップで変わってるかもしれないけど)変数が隣接されている。
elemでとったり、put_elemで置き換えたりできる。ただし、タプルもイミュータブル(改変不可)なので書き換わったタプルが返ってくる。

リストとタプルの使いどころ

リストの正体

今まで単純に[1, 2, 3]とか使ってたけど、ほんとは【値1、値2のポインタ】、【値2、値3のポインタ】、【値3、たぶん「終わりじゃ」という何か】という組み合わせでできているみたい。
なので、リストの先頭に左から追加するときは先頭の値しか見なくて良い 連結リストって訳してるのでよく分からなかったが、原文を読むと「Linked List」になってるので、納得。

list = [1, 2, 3]
[0] + list
# listの先頭のみプログラムが確認する
[0, 1, 2, 3]

逆に、ケツに値を追加すると全てのデータを参照しないといけないので遅い。膨大なデータになればなるほど致命傷になりそう。 n番目の要素を取得するのにもおそらくn番目までデータを参照しないといけないので遅い。

その分タプルはn番目の要素を取得したり、全体のサイズを取得するのが早いそうです。
代わりに値を更新したり追加したりすると、イミュータブルなのでタプルの作り直しになり、メモリを食う。
ただし、中身(変数やオブジェクト?)などは参照する形なので最低限のメモリ消費ですよ、とも記載がある。
おそらくタプルが保持する情報は【インデックス、ポインタ】で要素を参照しているので、{"hoge", "fuga"}を編集して{"hoge, "fugafuga"}にしても、一個目の要素"hoge"はメモリ上に一箇所しか展開されてませんよ。ということだろうか。
それであれば、メモリ上に増える要素はポインタを格納する変数になるので節約できる、ということだと思います。
この例だとメモリ節約できてんの?って感じしますが、hogeが何万文字もある文字列だったら変わりますよね。

この辺に関連する命名規則としてElixirでは「値が事前に計算されていそうなもの」であればsize、捜索が必要なものであればlengthを付ける傾向があるようです。
tuple_size/1 length String.length/1

もうちょっと色々使って慣れたいところです。