技術メモ

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

Elixir入門(第十二章 IOとファイルシステム)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
お次はIO and the file systemということで、ファイルIOなどでしょうか。

The IO module(IOモジュール)

IOモジュールはElixirの基本的なインプット/アウトプットの関数群のようです。
そういえばIO.puts/1とか既に出てきてますよね。

iex(7)> IO.puts "hello world"
hello world
:ok
iex(8)> IO.gets "yes or no? "
yes or no? yes
"yes\n"

pythonでいうprint()input()ですね。ここはまあ大丈夫。
IOモジュールは特に明示しない限りは標準入出力を使用するようですが、:stderrなど指定すれば標準エラー出力への出力もできるようです。
あまりWindowsでは意識したことありませんが・・・

iex> IO.puts :stderr, "hello world"
hello world
:ok

The File module(Fileモジュール)

Fileモジュールを利用すればファイルシステムを利用することができます。
これはどっかで既に出てきてた気がします。

iex> {:ok, file} = File.open "hello", [:write]
{:ok, #PID<0.47.0>}
iex> IO.binwrite file, "world"
:ok
iex> File.close file
:ok
iex> File.read "hello"
{:ok, "world"}

書き込み用にファイルを開く場合はFile.open ファイル名 [:write]みたいな感じでしょうか。
書き込み終了後に読み取りはいちいち開かなくて良さそうです。
File.openの返値にプロセスIDが返ってきてる所をみると、File.openもStoreの様に別プロセスで値を保持してるかんじでしょうか。

あと、UNIXのコマンドっぽい関数も実装されているようです。これは適宜調べれば良さそう。

File.readなどは2種類関数が用意されているようで、File.read!もあるみたいです。
一般的に{:ok, "hogehoge"}のようにタプルで返ってくるのが、中身だけ返ってくる関数のようです。

iex> File.read "hello"
{:ok, "world"}
iex> File.read! "hello"
"world"
iex> File.read "unknown"
{:error, :enoent}
iex> File.read! "unknown"
** (File.Error) could not read file "unknown": no such file or directory

ファイルが開けない場合はエラーがでると。
!無し版ならcaseでパターンマッチの条件分岐が書ける。

case File.read(file) do
  {:ok, body}      -> # do something with the `body`
  {:error, reason} -> # handle the error caused by `reason`
end

でもパターンマッチでエラーが起こるので十分って人はパターンマッチだけ書けば良さそう。

{:ok, body} = File.read(file)

いずれにしろ!無しの方がよさそう。

The Path module(Pathモジュール)

名前からして多分相対パス絶対パスの解決ツール?

iex> Path.join("foo", "bar")
"foo/bar"
iex> Path.expand("~/hello")
"/Users/jose/hello"

のようです。
コレがあるのと無いのとでは多種のOSで実行するときに結構違うので助かります。

Processes and group leaders

このセクションスキップして良いよって書かれてたけど、そうすると永遠に見ない気がするので一応みときます。

File.openでプロセスIDが返ってきてたのはやはり別プロセスで実行されているようで、IO.write(pid, binary)でメッセージを送る感じで書き込み可能?

iex(14)> pid = spawn fn ->
...(14)> receive do: (msg -> IO.inspect msg)
...(14)> end
#PID<0.124.0>
iex(15)> IO.write(pid, "hello")
{:io_request, #PID<0.102.0>, #Reference<0.1171378494.2642411524.2038>,
 {:put_chars, :unicode, "hello"}}
** (ErlangError) Erlang error: :terminated
    (stdlib) :io.put_chars(#PID<0.124.0>, :unicode, "hello")

IO.writeなどでメッセージングが規格化されているイメージでしょうか。
おそらく普段使いでは気にしなくていい気がします。

StringIOというモジュールに関して。
なんか使いどころが分かりませんが文字列もIOデバイスとして取り扱える?

iex> {:ok, pid} = StringIO.open("hello")
{:ok, #PID<0.43.0>}
iex> IO.read(pid, 2)
"he"

group leaderという特殊なIOデバイスが存在しているそうで、そいつに向けてメッセージを送信することもできる。
エラー処理とかに使う?

iex> IO.puts :stdio, "hello"
hello
:ok
iex> IO.puts Process.group_leader, "hello"
hello
:ok

標準入出力がgroup leaderらしいけど調整できるっぽい。
使いどころがイマイチわからないので時が来たらまた考えましょう。

iodata and chardata

今までの例ではバイナリ文字列を使ってましたが、charlistsや文字コードのリストでも行けるよ!って話。たぶん。

iex> IO.puts 'hello world'
hello world
:ok
iex> IO.puts ['hello', ?\s, "world"]
hello world
:ok

たぶんこういうバイナリ文字列やcharlists、文字列コードリストのことを総称してiodataとよんでおり(?)、iodataが引数として考えられている。
標準出力やファイルを開くときにエンコードを指定している場合はchar_dataという文字列などが引数として期待されている。
うーん、なんとなく分かるけど実感としてイマイチピンとこない。
おそらくバイナリデータとかを扱わないといまいちな箇所かも。 詰まったら勉強し直そう。