自作zipとlists:zip

今日は息抜きにちょっとErlang勉強.だんだんと再帰処理にも慣れて来たし,組み込み関数になければ自分で書けばいいか,という気分でけっこう楽しい.

僕は学生時代に情報工学科のLisp授業に潜って以来,Lispを何度も勉強して挫折を繰り返しているのだけれど結局,Lispで普通のプログラムを書くことができないでいる.Lispの勉強自体は他の言語を理解する上でとても役に立っているので,それ自体はあまり気にしていないのだけれど.(おかげで僕はけっこうなLispコンプレックス持ちになってしまった)

Erlangを書いていると,あぁそういうことか,Lispでもこう書けばよかったのか,とプログラミングのイメージが沸いてくる.

懐古話はさておき,いろんな言語でmap関数を使うたびに思うのだけれど,map関数が処理をするリストは基本的にひとつである.複数のリストを同時に処理したい場合には,後述するように工夫が必要となる.

Common Lispの場合,リストは複数あってもよい.(Emacs Lispではひとつだけ)

> (mapcar #'(lambda (x) (* x x)) '(1 2 3))
(2 4 6)
> (mapcar #'(lambda (x y z) (* x y z)) '(1 2 3) '(4 5 6) '(7 8 9))
(28 80 162)

こういうときにLispのmapcarは便利だと思う.


Erlangでもご多分に漏れず,ひとつのリストしか処理できなかったので,自分で書くことにした.これから長い? Erlangライフのためにこの関数が自作ライブラリmylib.erlの最初の関数になった.

-module(mylib).
-compile(export_all).

% Zip function
zip (_F, [], []) -> [];
zip (_F, [], _) -> [];
zip (_F, _, []) -> [];

zip (F, [H1|T1], [H2|T2]) ->
    [F(H1, H2) | zip(F, T1, T2)].

Lispでは(cons (car lis) (func (cdr lis))と書くところ,引数でHead, Tailを受け取ってしまうので,こんなにさらっと書ける.(え,マクロを使えば解決するって?)


例えば,こうやって使う.同じ長さでなかった場合には尻をきってくれるLispと同じ仕様.

> mylib:zip(fun(X, Y) -> io:format("~p ~p~n", [X, Y]) end, [1,2,3], [2,3,4,5]).
1 2
2 3
3 4


さて,ここまでやったところでぱらぱらとErlang本を眺めてlists:zipなる関数を知った.
こんな風にふたつのリストを与えると,タプルのリストにして返してくれる.長さの違うリストは例外を返す仕様らしい.

1> lists:zip([1,2,3], [4,5,6]).
[{1,4},{2,5},{3,6}]

> lists:zip([1,2,3], [4,5,6,7]).
2> lists:zip([1,2,3], [4,5,6,7]).
** exception error: no function clause matching lists:zip([],[7])
     in function  lists:zip/2

なので,上記に書いた自作zipを使わずとも,lists:zipを使って書ける.

> map(fun({X,Y}) -> io:format("~p ~p~n", [X, Y]) end, lists:zip([1,2,3], [2,3,4,5])).
1 2
2 3
3 4

さようなら自作zip.君のことは忘れないよ.


Erlangには可変長引数という概念がないのか,こういった場合,io:formatの第3引数みたいにリストで受け取って中で工夫するしかないのかなぁ.


Erlang楽しいなぁ.
僕の場合は叶わなかった初恋 (Lisp) を今の恋 (Erlang) に重ねてるんだろうけど.