% 長さ n のリストを生成して、その要素毎に何か(foreachの第一引数)をさせる
lists:foreach(fun(X)->io:format("hello\n") end,lists:seq(1,10)).
% リスト内包表記(結果のリストとして返される値は廃棄する)で実現
[io:format("hello\n")||X<-lists:seq(1,10)].
% 再帰関数で実現(下記はモジュール名と同名の単独ファイルにする必要あり)
-module(rep).
-export([rep/1]).
rep(0)->nil;
rep(X)->io:format("hello\n"),rep(X-1).
% 呼出は c(rep). のあと rep:rep(10). のように回数を数値で与える
# リスト(またはRange Stream などのオブジェクト)でループ
Enum.each(1..5,fn(x)->IO.puts("hello")end)
1..5|>Enum.each(fn(x)->IO.puts("hello")end)
# パイプで繋ぐ書き方
Range.new(1,5)|>Enum.each(fn(x)->IO.puts("hello")end)
# まだるっこしい書き方もある
1..5|>Stream.each(fn(x)->IO.puts("hello")end)|>Stream.run()
# Streamの形だと、Streamを実行する(run)まで出力の動作は行われない
# 内包表記で書く
for x<-1..5, do: IO.puts(x)
# 上記のリストによる記法で、eachの代わりにmap を使ったものと同じ # (putsメソッドの結果の値であるnilをリストの形で並べたものを値として返す)
# 再帰を使う場合
defmodule Rep do
def rep(0), do: 0
def rep(n) do; IO.puts(n) ; rep(n-1) ;end
end # 以上をファイルに用意してコンパイルする必要がある
Rep.rep(5) # この場合、整数を降順に印字する
;
で区切った), do:
の後ろに単独の式が書ける(略記法)Erlangでは、ファイルのOpen などはせずにいきなり読み込みができる
{ok, Data} = file:read_file("1.md").
io:format("~s",[Data]).
が、binary string(生データ)を返す関数なので、 使う時に変換が必要になる場合もある (上記のように印字するだけの時は問題ないが)。
ファイルを行単位で読む時には、file を open してから行うことになる。
% 再帰バージョン
-module(e1).
-export([cat/1]).
cat1(In) ->
case file:read_line(In) of
eof -> ok;
{ok, Line} -> io:format("~s",[Line]), cat1(In)
end.
cat(Filename) ->
case file:open(Filename, read) of
{error, Reason} -> io:format('file open error ~w~n', [Reason]);
{ok, In} -> cat1(In), file:close(In)
end.
Erlang には while のような繰り返しの構文は用意されておらず、 こういった繰り返しは再帰で書くことになるだろう。
なお、関数としてwhileを作ることはできるが、その場合は再帰で実現することになり、無理にwhileを作っても実質的には変わらないのでここでは扱わない。
Elixirの記法も示しておく。
# 簡便な書き方(open なしにいきなり読む)
File.read("1.md")
# 細やかな指定ができる方法
{:ok, file} = File.open("foo.tar.gz", [:read, :compressed])
IO.read(file, :line)
File.close(file)
File.open("1.md",fn file-> IO.read(f,:line) end) # => {:ok, line}
# 第2引数として渡した関数が実行されその結果がこの式全体の値となる
ついでながら、Erlang の io:read
と同様に、 Elixir
言語の文法に沿った term を読み込む関数もある。
File.consult(Filename)
Erlang:
% ワンタッチ版
file:write_file("xxx.txt", "この文字列が書き込まれます"). % => ok
% 丁寧版 ここでは回数を決めて出力を繰り返してみる
{ok, F} = file:open("xxx.txt",write),
[file:write(F,"hello\n")||X<-lists:seq(1,10)],
file:close(F). % やはり最後にcloseが必要
{ok, F} = file:open("xxx.txt",write),
Fn=fun(_)->
{_,{H,M,S}}=erlang:localtime(),
io:format(F,"~2.10.0B:~2.10.0B:~2.10.0B~n",[H,M,S]),
timer:sleep(5000)
end,
lists:foreach(Fn,lists:seq(1,10)),
file:close(F).
コンソールからの入力をファイルに書き出す。
-module(cp0).
-export([cp/0]).
cp1(F)->
case io:get_line("") of
eof->file:close(F);
"\n"->file:close(F);
Line->file:write(F,Line),cp1(F)
end.
cp()->
{ok, F} = file:open("xxx.txt",write),
cp1(F).
Elixir:
# ワンタッチ版
File.write("xxx.txt","abcdefg")
# 丁寧版
{:ok,f}=File.open("xxx.txt",[:write])
IO.write(f,"abcdefg")
File.close(f)
# 自動close
File.open("xxx.txt",[:write],fn f->
IO.write(f,"abcdefgh")
end)
まとめて読み、変数で受け渡す、逐次的考え方:
{ok,Data}=file:read_file("xxx.txt"),
file:write_file("xxx.txt.bak", Data).
{:ok,data}=File.read("xxx.txt")
File.write("xxx.txt.bak",data)
ファイルを分割して(たとえば行単位で)読む場合、 以下のようにいくつかの受け渡し方が考えられる。
リストの形で連結
-module(cp1).
-export([cp/1]).
rd(F,Ls)->
case io:get_line(F,'') of
eof -> Ls;
Line -> rd(F, Ls++[Line])
end.
cp(Filename)->
{ok,Ifile}=file:open(Filename,read),
List=rd(Ifile,[]),
file:close(Ifile),
% ここまでが入力 このあと出力
{ok,Ofile}=file:open(Filename++".bak"),
[io:write(Ofile,E)||E<-List],
file:close(Ofile).
++
を使用している。defmodule Cp1 do
defp rd(f,ls) do
case IO.read(f,:line) do
:eof -> ls
line -> rd(f, ls++[line])
end
end
def cp(filename) do
{:ok,list}=File.open(filename,[:read],fn ifile->
rd(ifile,[])
end)
# IO.puts(list)
File.open(filename<>".bak",[:write],fn ofile->
for e<-list , do: IO.write(ofile,e)
end)
end
end
<>
で連結する。1行ずつ、同時進行で読み書き
-module(cp2).
-export([cp/2]).
cp1(Ifile,Ofile)->
case io:get_line(Ifile,'') of
eof->file:close(Ofile),file:close(Ifile);
Line->file:write(Ofile,Line),cp1(Ifile,Ofile)
end.
cp(Filename,Newname)->
{ok, Ifile} = file:open(Filename,read),
{ok, Ofile} = file:open(Newname,write),
cp1(Ifile,Ofile).
defmodule Cp2 do
defp cp1(ifile,ofile) do
case IO.read(ifile,:line) do
:eof->File.close(ofile); File.close(ifile)
line->IO.write(ofile,line); cp1(ifile,ofile)
end
end
def cp(filename,newname) do
{:ok, ifile} = File.open(filename,[:read])
{:ok, ofile} = File.open(newname,[:write])
cp1(ifile,ofile)
end
end
reader, writer を別プロセスにしてプロセス間通信で受け渡す
-module(cp3).
-export([cp/2,writer/1]).
writer(Ofile)->
receive
eof->file:close(Ofile);
Line->file:write(Ofile,Line), writer(Ofile)
end.
cp1(Ifile,Pid)->
case io:get_line(Ifile,'') of
eof->Pid!eof,file:close(Ifile);
Line->Pid!Line, cp1(Ifile,Pid)
end.
cp(Filename,Newname)->
{ok,Ofile} = file:open(Newname,write),
{ok,Ifile} = file:open(Filename,read),
Pid=spawn(cp3,writer,[Ofile]),
cp1(Ifile,Pid).
defmodule Cp3 do
def writer(ofile) do # この関数が別プロセスとしてspawnされる
receive do # メッセージを受信
:eof->File.close(ofile)
line->IO.write(ofile,line); writer(ofile)
end
end
defp cp1(ifile,pid) do
case IO.read(ifile,:line) do # 読んだ行をそのまま送信
:eof->send(pid,:eof)
line->send(pid,line); cp1(ifile,pid)
end
end
def cp(filename,newname) do
{:ok, ofile} = File.open(newname,[:write])
File.open(filename,[:read],fn(ifile)->
pid=spawn(Cp3,:writer,[ofile])
cp1(ifile,pid)
end)
end
end
(いずれの言語でも)関数writerを別プロセスとしてspawn し、
そのプロセスの識別子 pid を親プロセス(reader側)で認識しておき、
読み込んだ行データをそのまま pid側に(プロセス間通信の機能で)送信(send)すると、
writer 側がそれを receive で受け取って、ファイルに書く、という動作をしている。
つまり reader, writer の2つのプロセスが並行して動作していることになる。
Erlang/Elixir ではこのように複数プロセスの協調動作でプログラムを作ることがよく行われる。
Elixirには Stream の機能が提供されているので、明示的に別プロセスにせずに並行動作を実現することもできる。
defmodule Cp4 do
def cp(filename,newfile) do
File.open(newfile,[:write],fn(ofile)->
filename|>
File.stream!([:read],:line)|>
Stream.each(fn line->
IO.write(ofile,line)
end)|>
Stream.run()
end)
end
end