Elixir_modules
modules
ドキュメント参照したモノ等をコンテンツとして記録しておく..
ドキュメント参照したモノ等をコンテンツとして記録しておく..
前章までに リスト と マップ というコレクションとして振る舞う型を見てきた。他にもいくつもあるらしい。
共通的なコトは、コレクションが持っている要素?に対して、繰り返し処理ができるということ。 `Enumerableプロトコルを実装しているといえる` という表現をしている。
Streamモジュールは.. 少し先に見たことがある、パイプ演算子を挟んで処理の並列化で前段の終了を待たずにデータを流し込む、遅延処理してくれるようなもの...かな.
詳しくは Elixir schoolのEnumの章を読むとして、おおよそ以下の機能をもつ:
・・・・
期待値
iex(5)> Enum.all?( [2,4,6], &(rem(&1,2)==0) )
true
試行錯誤の結果
defmodule MyEnum do
defp _all([_], false, _), do: false
defp _all([_], nil, _), do: false
defp _all([], _, _), do: true
defp _all([h|t], _, f), do: _all(t, f.(h), f)
def all?([h|t], f), do: (
_all(t, f.(h), f)
)
end
おけ
iex(7)> MyEnum.all?( [2,4,6], &(rem(&1,2)==0) )
true
期待値
iex(9)> Enum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok
defmodule MyEnum do
defp _each([h], func), do: func.(h)
defp _each([h|t], func), do: (
func.(h)
_each(t,func)
)
def each([h|t], func), do: (
_each([h|t], func)
)
end
iex(18)> MyEnum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok
defmodule MyEnum do
defp _filter([], _func, result), do: result
defp _filter([h|t], func, result), do: (
if func.(h) do
_filter(t, func, result++[h])
else
_filter(t, func, result)
end
)
def filter([h|t], func), do: (
_filter([h|t], func, [])
)
end
iex(31)> MyEnum.filter([1, 2, 3,4,5,6,7], fn x -> rem(x, 2) == 0 end)
[2, 4, 6]
不具合
誤: _filter(t, func, [result|h])
正: _filter(t, func, result++[h])
resultがリストであることを期待して記述したつもり。縦棒は要素を区切るものだったから 誤りではリストと新しいスカラ値の2要素のリストが生成された。
リストに要素を追加する場合は `++` 演算子を使う。
MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
iex(22)> MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
[[] | 2]
TODO: あとで作る? リスト、番号を引数に持つ。
ローカル関数で結果のタプルを引数に渡して呼び出す。要素数を自然数で受けるようにして、前からいくつめかを認識させ、1ずつ減らしてヘッドから要素を取り出していく。結果のタプルを都度更新していけば結果が得られるだろう。
TODO: あとで作る?
splitと似たような実装。結果は要素ひとつにすれば同じように作れる?(共通化は?)
Enumはデータすべてを処理しきる必要があり、パイプ演算子で結合した段それぞれで処理の完了を待つ。ストリーム対応であれば、処理の完了を待たずに逐次出力されたデータを次の段に渡していく。最終的にListに入れる場合などは、すべてのデータが入力されるのを待つ処理が入る。
・・・・そんな感じ?
defmodule Canvas do
@defaults [ fg: "black", bg: "white", font: "Arial" ]
def draw_text( text, options \\ [] ) do
options = Keyword.merge(@defaults, options)
IO.puts "Drawing test #{inspect(text)}"
IO.puts "Foreground: #{options[:fg]}"
IO.puts "Background: #{Keyword.get(options, :bg)}"
end
end
iex(3)> Canvas.draw_text( "test", fg: "red" )
Drawing test "test"
Foreground: red
Background: white
:ok
iex(5)> Canvas.draw_text( "test", [])
Drawing test "test"
Foreground: black
Background: white
:ok
iex(6)> Canvas.draw_text( "test")
Drawing test "test"
Foreground: black
Background: white
:ok
キーと値のセットというデータ構造を扱うもの。制限が緩いので多用途?
iex(1)> defaults = %{ fg: "black", bg: "white", font: "Arial" }
%{bg: "white", fg: "black", font: "Arial"}
iex(2)> Map.keys defaults
[:bg, :fg, :font]
iex(3)> Map.values defaults
["white", "black", "Arial"]
iex(6)> Map.drop defaults , [:font, :bg]
%{fg: "black"}
iex(7)> Map.put defaults, :sound, "none"
%{bg: "white", fg: "black", font: "Arial", sound: "none"}
iex(8)> defaults
%{bg: "white", fg: "black", font: "Arial"}
iex(9)> Map.has_key? defaults, :fg
true
iex(10)> Map.has_key? defaults, :ssg
false
iexから h ... で説明も得られる. popの返り値は 値と新しいmapのタプルだ. キーがあれば, popで取得した key, valueのセットを除いたmapを返してくれる. キーが見つからなければそのまま, 値は第三引数を返す(省略するとnilね).
iex(11)> h Map.pop
def pop(map, key, default \\ nil)
@spec pop(map(), key(), value()) :: {value(), map()}
Returns and removes the value associated with key in map.
If key is present in map with value value, {value, new_map} is returned where
new_map is the result of removing key from map. If key is not present in map,
{default, map} is returned.
## Examples
iex> Map.pop(%{a: 1}, :a)
{1, %{}}
iex> Map.pop(%{a: 1}, :b)
{nil, %{a: 1}}
iex> Map.pop(%{a: 1}, :b, 3)
{3, %{a: 1}}
前述の Map.pop を使わなくても, パターンマッチで値を取得できる.
ただし キーがなければそこでエラー終了してしまう..
iex(12)> %{ fg: col } = defaults
%{bg: "white", fg: "black", font: "Arial"}
iex(13)> col
"black"
iex(14)> %{ bgm: col } = defaults
** (MatchError) no match of right hand side value: %{bg: "white", fg: "black", font: "Arial"}
Elixirは Immutableな変数を扱うので, 更新処理は右辺で行う。すなわち、もともとのmap変数の中を上書きすることはなく、変更した結果のマップを新たに左辺の変数 or パターンで束縛する(?束縛の意味があやしい)。
※パターンマッチ(左辺をマップ式で受ける?)で、キーワードを設定すれば それを含むものだけがマッチする。複数指定も可能。
Mapの制限を付与したデータ構造。構造体にすると, Mapに比べて以下の制限がある
defmodule ImageHeader do
defstruct magic: 0xCafeBabe, length: <<0::size(32)>>, body: <<>>
def set_magic(base=%ImageHeader{}, <<value>>), do: %ImageHeader{base | magic: <<value>>}
def is_valid_magic(%ImageHeader{magic: val}), do: val == 0xDeadBeef
end
iex(3)> a = %ImageHeader{}
%ImageHeader{body: "", length: <<0, 0, 0, 0>>, magic: 3405691582}
iex(4)> ImageHeader.is_valid_magic(a)
false
iex(5)> aa = %ImageHeader{a | magic: 0xdeadbeef}
%ImageHeader{body: "", length: <<0, 0, 0, 0>>, magic: 3735928559}
iex(6)> ImageHeader.is_valid_magic(aa)
true
入れ子記述は容易に気付けるだろう。問題は階層深くのメンバへのアクセス方法。変更を加える場合には、元のデータを渡していく必要があるので、階層分すべての構造体を理解してマッチングして...記述しなければならない.
簡略化のためのマクロ・関数が用意されている。
put_in(var.var1.var2, "new value")
ここで
関数を渡して引数に元の値、結果に書き換えてくれるマクロ update_in()がある. 取得するための get_in(), 取得して更新する get_and_update_in(),...
ヘルプを見て あとは実践(TODO)..
この構造体や moduleに閉じ込めた 関数について、オブジェクト指向ぽく記述ができそう、というのはよろしく無いとある。まさにそう考えていただけにガツーンですよまったく。
Elixirは関数型言語、その特徴・メリットを知る(第九章、1ページ)
構造体名のとおり、本来はバイナリを扱いたかった。 c++のクラスとは異なり、すべて static method相当になってしまうのかな. thisポインタの代わりに 明示的にインスタンスを受けて, 変更後のインスタンスを返すような作りになりそう. 何らかの表現を返すなら タプルにすれば解決するだろう...
値をもらって バイナリ列に変換したいだけなのかな. ライブラリとかありそう.
defmodule ImageHeader do
defstruct magic: <<0 :: size(32)>>, length: <<0::size(32)>>, body: <<>>
def set_magic(base=%ImageHeader{}, <<value>>), do: %ImageHeader{base | magic: <<value>>}
defp set_magic_oct(value) when value >= 256, do: (
<<rem(value,256) , set_magic_oct( div(value, 256) )>>
)
defp set_magic_oct(value) when value < 256, do: <<value>>
def set_magic(base=%ImageHeader{}, value), do: (
%ImageHeader{base | magic: set_magic_oct(value)}
)
def is_valid_magic(%ImageHeader{magic: val}), do: val == <<0xDe,0xad,0xBe,0xef>>
end
リスト中、縦棒をはさむことでリストの結合を表現できる?。今までの表現では、縦棒が出てこなかったけれども省略して書いていたと思えばよいだろう。 Lispのリストがこんなのだった気がする。要素1つと次へのポインタみたいな塊(間違ってたらごめんなさい)
iex(1)> [1,2,3,4]
[1, 2, 3, 4]
iex(2)> [ 1 | [ 2 | [ 3 | [ 4 | [] ] ] ] ]
[1, 2, 3, 4]
iex(3)> [h|t]=[ 1 | [ 2 | [ 3 | [ 4 | [] ] ] ] ]
[1, 2, 3, 4]
iex(4)> h
1
iex(5)> t
[2, 3, 4]
iex(6)> [h|t] = [1,2,3,4]
[1, 2, 3, 4]
iex(7)> h
1
iex(8)> t
[2, 3, 4]
[head|tail] で受けるようだけれど, tailに残りすべてが放り込まれる.
要素数ゼロだとエラーになる。縦棒があるので要素数が1つはあることをマッチング条件としているからかな..
iex(11)> [h|t]=[1]
[1]
iex(12)> h
1
iex(13)> t
[]
iex(14)> [h|t]=[]
** (MatchError) no match of right hand side value: []
defmodule TestList do
def len([]), do: 0
def len([_head|tail]), do: 1 + len(tail)
end
iex(15)> TestList.len( [ 4, 5, 67, 333] )
4
※関数引数名を "_" で始めると、その変数は未使用であることを宣言する。未使用引数はコンパイル時に警告が出る。
引数に渡された数値リストを、要素の値を二乗したリストを返す関数を考える:
defmodule TestList do
def squre([]), do: []
def squre([head|tail]), do: [ head*head, squre(tail)]
end
iex(3)> TestList.squre( [1,2,3,4])
[1, [4, [9, [16, []]]]]
リストになっていない... カンマで区切ると, squre()の返り値を1つの要素としてしまうのね.
defmodule TestList do
def squre([]), do: []
def squre([head|tail]), do: [ head*head | squre(tail)]
end
iex(5)> TestList.squre( [1,2,3,4])
[1, 4, 9, 16]
なるほど, 縦棒は偉大なり..
Enum.map が存在するが、これを自作してみようという話.
リストの要素ごとに 任意の関数を呼び出して結果をリストとして返す.
squreの場合は fn (x) -> x*x end を実行させたようなものだ.
defmodule TestList do
def map([], _func), do: []
def map([head|tail], func), do: [ func.(head) | map(tail, func)]
end
iex(8)> TestList.map [1,2,3], &(&1*&1)
[1, 4, 9]
iex(9)> TestList.map [1,2,3], fn (x) -> x*x end
[1, 4, 9]
なるほど、素直に作れますね... 自作の場合、マッチングに制限をかけるとかして 想定外の結果を返さないようにするなどできそうね.
上までは リストを受けて同じ要素数のリストを返す方法をみてきた. リスト全体の要素を見て、結果を返すことを考える. 簡単な例で数列の和を返す sum() 関数を実装する.
defmodule TestList do
def sum([], total), do: total
def sum([head|tail], total), do: sum(tail,head+total)
end
iex(2)> TestList.sum([1,2,3,4,5,6,7,8,9,10],0)
55
なるほど, よさそうだ. リストの前方から順に結果を得られる場合(リスト後方の値に依存しない場合)には有用そうだ. 数列だけではなくいろいろと妄想がはかどるが, 基本的には 状態 を 引数として渡すしかないようである.
これには 連想配列やキーワードリストが効果的な気がするね..
次にこれをリファクタリングする:
defmodule TestList do
def sum(list), do: _sum(list,0)
defp _sum([], total), do: total
defp _sum([head|tail], total), do: _sum(tail,head+total)
end
モジュール外から使用するときに, 状態保持用の第二引数をいちいち記述する必要がないからだ。計算の途中から実行したいという需要はあるかもしれないが、それはそういうインタフェースを作ればよいだろう.
デフォルトパラメータとして sum(list, total \\ 0) としないのは、内部でのみ扱う 状態 を外に意識させないためだろう.
プライベート関数の宣言に defp を使っていることに注目するとよい.
状態が1つであれば そのまま関数の返り値として扱えるかな。関数の処理成功/失敗や
defmodule TestList do
def sum([]), do: 0
def sum([head|tail]), do: (
head + sum(tail)
)
end
iex(3)> TestList.sum( [1,2,3,4,5,6,7,8,9,10])
55
sumの場合, 2つの値の和を返す処理を実装していた。この部分を汎用化すると, 関数を渡すことになるだろう。
defmodule TestList do
def reduce([], value, _func), do: value
def reduce([head|tail], value, func), do: reduce(tail, func.(head,value), func)
end
iex(2)>TestList.reduce [1,2,3,4,5], 0, fn m,n -> m+n end
15
defmodule People do
def males([]), do: []
def males([[name, :male, age]|tail]), do: [[name, :male, age] | males(tail)]
def males([_|tail]), do: males(tail)
def test_data do
[
["YNK", :male, 18],
["John", :male, 21],
["Mary", :female, 19],
["Beck", :male, 32]
]
end
end
性別を選べるようにしよう:
defmodule People do
def filter([], _sex), do: []
def filter([[name, sex, age]|tail], sex), do: [[name, sex, age] | filter(tail,sex)]
def filter([_|tail], sex), do: filter(tail,sex)
# ... test_data
end
関数の引数で与えた値が パターンマッチにも適用できている... 後方で与えている引数なのに, 前方のマッチングにも使われる.のか..
iex(5)> filter test_data, :male
[["YNK", :male, 18], ["John", :male, 21], ["Beck", :male, 32]]
iex(6)> filter test_data, :female
[["Mary", :female, 19]]
iex(7)> filter test_data, :none
[]
defmodule People do
def filter([], _sex), do: []
def filter([head=[_, sex, _]|tail], sex), do: [ head | filter(tail,sex)]
def filter([_|tail], sex), do: filter(tail,sex)
# ... def test_data do
end
リストのマッチングを head=で受けている。名前付き前方参照みたいな書式で、マッチングしたリストを 関数内で headとして参照できる. いちいち関数引数のパラメタを書き直す必要がないので 便利が良い.
fromからtoまでの数のリストを返す関数を書いてみる.
defmodule Test do
def span(from,to) when from<to, do: [from | span(from+1,to)]
def span(to,to), do: [to]
def span(_from,_to) when _from>_to, do: []
end
iex(4)> Test.span(4,3)
[]
iex(5)> Test.span(4,4)
[4]
iex(6)> Test.span(2,6)
[2, 3, 4, 5, 6]
whenを使っていいのかな..
ざっくりと紹介されている. helpで眺めるとかするとよいか.
困ったら 本家 を見るとよさそうね..
名前付き関数を定義するには、その親になるモジュールを定義する必要がある。モジュールなくしては名前付き関数を定義できない。
FILE: times.exs
defmodule Times do
def double(n) do
n * 2
end
end
以下の三種類すべて同じ記述方法。 "do: block" が基本で、あとはシンタックスシュガーぽい。"()"はブロックの記述か。
def double(n), do: n*2
def double(n), do
n*2
end
def double(n), do: (
n*2
)
重複するので割愛。コンパイル方法:
defmodule Factorial do
def of(0), do: 1
def of(n), do: n * of (n-1)
end
iex内でも module定義できますね..
iex(8)> defmodule Factorial do
...(8)> def of(0), do: 1
...(8)> def of(n), do: n * of (n-1)
...(8)> end
{:module, Factorial,
<<70, 79, 82, 49, 0, 0, 4, 60, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 130,
0, 0, 0, 15, 16, 69, 108, 105, 120, 105, 114, 46, 70, 97, 99, 116, 111, 114,
105, 97, 108, 8, 95, 95, 105, 110, 102, ...>>, {:of, 1}}
iex(9)> Factorial.of 3
6
iex(10)> Factorial.of 7
5040
iex(11)> Factorial.of 10
3628800
関数定義順にパターンマッチが試みられるようである。先食いするような記述はElixirが警告を出す(後に定義した関数が呼ばれることがない旨)。
関数の引数をチェックする。パターンマッチでは型まで判定できないので、whenキーワードを使う:
defmodule Factorial do
def of(0), do: 1
def of(n) when is_integer(n) and n>0, do: n * of (n-1)
end
ここでは 自然数であることを確認している。
is_*() で変数の型を知ることができる。タブ補完してみると、以下が見える:
iex(1)> is_
is_atom/1 is_binary/1 is_bitstring/1 is_boolean/1
is_float/1 is_function/1 is_function/2 is_integer/1
is_list/1 is_map/1 is_nil/1 is_number/1
is_pid/1 is_port/1 is_reference/1 is_tuple/1
Erlang/OTP 21 [erts-10.2.4] [source] [64-bit] [smp:48:48] [ds:48:48:10] [async-threads:1]
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
デフォルトパラメータは バックスラッシュ二つ( \ ) をパラメータ変数の後ろにおいて、値を続ける。
パラメータのマッチングは先頭から順次マッチングがとられる。必須パラメータのマッチングが行われ、不足する場合はエラーとなる。デフォルトパラメータが複数あり、呼び出し時に一部が渡される場合は、デフォルトパラメータが定義されている変数の先頭から順に適用される。
defmodule TestModule do
def func( p1, p2 \\ 2, p3 \\ 3, p4 ), do: IO.inspect [p1,p2,p3,p4]
end
iex(4)> TestModule.func 11,22,33,44
[11, 22, 33, 44]
[11, 22, 33, 44]
iex(5)> TestModule.func 11,22,33
[11, 22, 3, 33]
[11, 22, 3, 33]
iex(6)> TestModule.func 11,22
[11, 2, 3, 22]
[11, 2, 3, 22]
iex(7)> TestModule.func 11
iex(13)> c "guess.exs"
warning: redefining module TestModule (current version defined in memory)
guess.exs:1
[TestModule]
iex(14)> TestModule.guess 273 , 1..1000
Is it 500
Is it 250
Is it 375
Is it 312
Is it 281
Is it 265
Is it 273
finish!
:ok
if文つかわずに... ということでちょっと型チェックが緩いけれどこんな感じ? FILE: guess.exs
defmodule TestModule do
def get_range(a,m,s,_) when a<m, do: s..m
def get_range(a,m,_,e) when a>m, do: m..e
def get_range(a,_,_,_), do: a..a
def guess(actual, s..e) when s<e, do: (
m = div(s+e, 2)
IO.puts "Is it #{m}"
guess(actual, get_range(actual, m , s, e))
)
def guess(_,_), do: IO.puts "finish!"
end
スコープがモジュールに収丸場合、 def ではなく、 defp を用いる。同じ関数名で 混在することはできない(同じスコープにしないとマッチングが取れないよね)
|> )関数の返り値を、次の関数の第一引数に取るとき、パイプ演算子が使える。
func1 |> func2
モジュール名を引数にして import ModuleName と記述する。
ModuleName.func のモジュール名を記述せず、 func と書ける。
入れ子になったモジュール名を、任意の代替名で参照できる。
alias Module.My.Device, as: MyDev
alias Module.My.Device, as: Device
alias Module.My.Device
下の二つは同じ意味で、as:を省略すると、モジュール名の最後のドット以降をaliasとして宣言したことになる。
モジュールで定義したマクロを使うときに、requireしておくといいらしい。
TODO: マクロの勉強
モジュール定義のスコープ内で、以下のように宣言する。モジュール内の関数から @name で参照できる。
@name value
属性 name の値 value, と読み, おおむねモジュールによって定まる定数や authorなどの文字列を記述するのが慣例のようだ.
モジュール名は、内部処理でアトム :"モジュール.サブモジュール...." と表現され、関数呼び出しには、アトム+ "." + モジュールの関数 として扱われる。
Erlangの関数を呼び出す場合は、Elixirのモジュール名がCamelCaseであるのと対照的に、小文字で始まる。これによりElixirとErlangのモジュールを区別しているようである.
Erlangの io モジュール, format関数を呼ぶ例:
:io.format("format is not equals to C lang.~n")