無題の備忘録

IT技術について調べたことや学んだこと、試したこと記録するブログです。Erlang、ネットワーク、 セキュリティ、Linux関係のものが多いです。

Erlang の lists モジュールを一通り試す - その2

最近、Erlang のコードを読んでいて、lists:any という関数が出てきたのだが、知らなかったので調べた。ついでに、他の関数も一通り眺めておく。

公式URL: http://erlang.org/doc/man/lists.html

filter/2 関数

filter(Pred, List1) -> List2

Types

Pred = fun((Elem :: T) -> boolean())
List1 = List2 = [T]
T = term()

List2は、Pred(Elem) が true を返す List1 のすべての要素 Elem のリストである。

> IsAtom = fun(X) -> is_atom(X) end.
#Fun<erl_eval.6.99386804>
> lists:filter(IsAtom, [a,b,c,d,e]).
[a,b,c,d,e]
> lists:filter(IsAtom, [a,1,c,2,3]).
[a,c]
> lists:filter(IsAtom, [1,2,3,4,5]).
[]

第1引数で指定した関数が true を返す予想だけにフィルタリングしてくれる。上記の例では第2引数に指定したリストのうち、atomだけにして返してくれる。

filtermap/2 関数

filtermap(Fun, List1) -> List2

Types

Fun = fun((Elem) -> boolean() | {true, Value})
List1 = [Elem]
List2 = [Elem | Value]
Elem = Value = term()

Fun/1 はブール値またはタプル{true, Value}を返す必要がある。この関数は、第2引数に与えられたリストの要素を1つずつ Fun/1 の引数にいれて、値をチェックします。Fun/1の返却値が true の場合は、その値をそのまま返すリストに含める。false の場合は返すリストに含まない。{true, Value}の場合は、引数の値の代わりに Value を返すリストに含める。

filter/2 関数と map/2 関数を同時に処理するような関数である。

> F = fun(X) ->                             
>     case X rem 3 of                       
>         0 -> {true, 3};                   
>         1 -> true;                        
>         _ -> false                        
>     end
> end.
#Fun<erl_eval.6.99386804>
> lists:filtermap(F, [1,2,3,4,5,6,7,8,9]).
[1,3,4,3,7,3]

上の例は、特に意味がある処理はしていないが、3の倍数は{true, 3}により3になるようにして、3で割って1余る数字は true でそのまま残す。それ以外は削除するリストを作成する例である。

flatlength/1 関数

flatlength(DeepList) -> integer() >= 0

Types

DeepList = [term() | DeepList]

length(flatten(DeepList))と同等であるが、より効率的な関数。length(flatten(DeepList))と書いているところがあれば、置き換えよう。ネストしたリストを1階層のリストにしてその長さを返してくれる。

> lists:flatlength([1,2,[3,4,[5,6,7]],[8,9]]).     
9

flatmap/2 関数

flatmap(Fun, List1) -> List2

Types

Fun = fun((A) -> [B])
List1 = [A]
List2 = [B]
A = B = term()

第1引数のFun/1 はリストを返す必要がある。List1 の要素に Fun/1を適用した結果、リストのリストができるが、それにappend/1 をかけて1階層のリストにしたものを返す。

第2引数のリストに Fun/1 を map したあと、リストを flat するようなイメージ。

> F = fun(X) -> [X,X] end.    
#Fun<erl_eval.6.99386804>
> lists:flatmap(F, [a, b, c]).
[a,a,b,b,c,c]

Fun/1 がリストを返さない関数の場合、bad argument (badarg)が発生する。

> F = fun(X) -> X end.        
#Fun<erl_eval.6.99386804>
> lists:flatmap(F, [a, b, c]).
** exception error: bad argument
     in operator  ++/2
        called as c ++ []
     in call from lists:flatmap/2 (lists.erl, line 1250)
     in call from lists:flatmap/2 (lists.erl, line 1250)

flatten/1 関数

flatten(DeepList) -> List

Types

DeepList = [term() | DeepList]
List = [term()]

引数の DeepList をフラット化したリストを返す。DeepList はリストの要素にリストがあるリストのことで、リストの入れ子構造になっている。例えば、[1,2,[3,4,[5,6,7]],[8.9]] な感じだ。

> DeepList = [1,2,[3,4,[5,6,7]],[8.9]].
[1,2,[3,4,[5,6,7]],[8.9]]
> lists:flatten(DeepList).
[1,2,3,4,5,6,7,8.9]

flatten/2 関数

flatten(DeepList, Tail) -> List

Types

DeepList = [term() | DeepList]
Tail = List = [term()]

Tail のリストが DeepList に追加されたフラット化されたリストを返す。

> Tail = [a,b,c].
[a,b,c]
> DeepList = [1,2,[3,4,[5,6,7]],[8.9]].
[1,2,[3,4,[5,6,7]],[8.9]]
> lists:flatten(DeepList, Tail).
[1,2,3,4,5,6,7,8.9,a,b,c]

ただし、Tail = List = [term()]より、Tail はフラット化されない点に注意。

> DeepTail = [a, b, [c,d]].     
[a,b,[c,d]]
> lists:flatten(DeepList, DeepTail).
[1,2,3,4,5,6,7,8.9,a,b,[c,d]]

foldl/3 関数

foldl(Fun, Acc0, List) -> Acc1

Types

Fun = fun((Elem :: T, AccIn) -> AccOut)
Acc0 = Acc1 = AccIn = AccOut = term()
List = [T]
T = term()

リストの連続する要素Aで Fun(Elem、AccIn) を呼び出し、AccIn == Acc0で開始する。 Fun/2 は、次の呼び出しに渡される新しいアキュムレーターを返さなければならない。つまり、AccOut はリストの次の要素を引数にするときに AccIn として使われる。この関数は、アキュムレーターの最終値を返す。 リストが空の場合、Acc0が返される。

別の言い方をすると、リストの要素に順番に Fun/2 を適用するが、そのときに1つ前のリストの要素の結果を受け取る。 Fun/2 は適用した結果を返すが、その値は次のリストの要素に Fun/2 を適用するときに使われるというものだ。最後の要素に適用した Fun/2 の結果が、この関数の返す値である。

リストの要素を順番に足す処理などは簡単な例だ。

> F = fun(X, Sum) -> X + Sum end.
#Fun<erl_eval.12.99386804>
> lists:foldl(F, 0, [1,2,3,4,5]).
15

F/2 は、リストの要素 X にバインドし、AccIn は Sum、 つまり合計値になる。 F/2 の計算 X + Sum (AccOut)をしている。その値 AccOut は、次の F/2 呼び出しの Sum として使われるといった具合だ。Acc0 = 0 を指定しているが、これは1つ目のリストの要素を処理するときに Sum = 0 として初期値を与えている。

foldr/3 関数

foldr(Fun, Acc0, List) -> Acc1

Types

Fun = fun((Elem :: T, AccIn) -> AccOut)
Acc0 = Acc1 = AccIn = AccOut = term()
List = [T]
T = term()

foldl/3に似ているが、この関数はリストを右から左に処理する。

> F = fun(X, AccIn) -> io:format("~p ", [X]), AccIn end.
#Fun<erl_eval.12.99386804>
> lists:foldl(F, ignore, [1,2,3,4,5]).                  
1 2 3 4 5 ignore
> lists:foldr(F, ignore, [1,2,3,4,5]).
5 4 3 2 1 ignore

上の例は、foldr/3の例というよりは、処理の順番がどのようになっているかを示している。F/2io:format/2 関数を使って X の値をプリントしている。プリントの順番がリストの要素が処理される順番になる。fold/3ではリストの左から右の順番で要素が処理されていることがわかる。foldr/3では要素の右から左の順番に処理されていることがわかる。

forearch/2 関数

foreach(Fun, List) -> ok

Types

Fun = fun((Elem :: T) -> term())
List = [T]
T = term()

リスト内の各要素 Elem に対して Fun(Elem) を呼び出す。この関数は副作用のために使用される。そのため関数の返り値は ok のみ。評価順序はリスト内の要素の順序と同じになる。

Fun/1 によって、何かの状態を変更するような場合に使うと思う。非同期メッセージを送ったり、データベースの値を書き換えたり、ファイルを削除したりするときに使うかな。

例えば、下記のようなに、ディレクトリのファイルを削除したいときに使える。

$ touch file1 file2 file3 file4 file5
$ erl
...(省略)
> F = fun(F) -> file:delete(F) end.
#Fun<erl_eval.6.99386804>
> Files = filelib:wildcard("*").
["file1","file2","file3","file4","file5"]
> lists:foreach(F, Files).
ok
> filelib:wildcard("*").
[]

join/2 関数

join(Sep, List1) -> List2

Types

Sep = T
List1 = List2 = [T]
T = term()

List1 の各要素の間に Sep を挿入したリストを返す。空のリストや要素が1つのリストには影響しない。

> lists:join('/', [a,b,c,d,e]).
[a,'/',b,'/',c,'/',d,'/',e]
> lists:join('/', [a]).
[a]
> lists:join('/', []). 
[]