みずりゅの自由帳

主に参加したイベントやソフトウェア技術/開発について記録しています

Elixirの関数名に感嘆符をつける意味

備忘。
Elixirの命名規約を見直していて、感嘆符(かんたんふ/exclamation mark(エクスクラメイション マーク))*1こと「!」が末尾についた関数をあまり意識していなかったので再確認。

その意味は「例外が発生する可能性のある関数/マクロに対して、習慣的に付与される*2」です。


公式ドキュメントによると、以下のように書かれています。*3

A trailing bang (exclamation mark) signifies a function or macro where failure cases raise an exception.

Many functions come in pairs, such as File.read/1 and File.read!/1. File.read/1 will return a success or failure tuple, whereas File.read!/1 will return a plain value or else raise an exception:

日本語に訳すと、以下のようになります。*4

最後のバング(感嘆符)は、失敗した場合に例外が発生する関数やマクロを意味します。

多くの関数は、File.read/1やFile.read!/1のように、ペアになっています。
File.read/1 は成功または失敗のタプルを返しますが、File.read!/1 は単純な値を返すか、例外を発生させます。

hexdocs.pm

これは、書籍「プログラミングElixir」でも触れられていました。*5


HexDocsの例にあるように、存在するファイル/存在しないファイルのそれぞれに対して「FIle.read/2」と「FIle.read!/2」を利用しました。
結果は以下のとおりです。

ファイルが存在する場合:

  • FIle.read/2 : 処理ステータス(:ok)とファイル内容データが、タプル形式で返却されます。
  • FIle.read!/2 : 処理ステータスはなく、ファイル内容データだけが返却されます。
iex> {status, contents} = File.read("lib/tail_recursive.ex")
{:ok,
 "defmodule TailRecursive do\n  (〜中略〜) def sum(n), do: n + sum(n-1)\nend\n"}

iex> new_contents = File.read!("lib/tail_recursive.ex")
"defmodule TailRecursive do\n   (〜中略〜)  def sum(n), do: n + sum(n-1)\nend\n"

iex> contents == new_contents
true

ファイルが存在しな場合:

  • FIle.read/2 : 処理ステータス(:error)とファイル内容がない旨を示すアトム(:enoent)が、タプル形式で返却される。
  • FIle.read!/2 : File.Errorという例外が発生する。
iex> {status, contents} = File.read("lib/file_not_exist.ex")
{:error, :enoent}

iex> contents = File.read!("lib/file_not_exist.ex")
** (File.Error) could not read file "lib/file_not_exist.ex": no such file or directory
    (elixir 1.10.4) lib/file.ex:353: File.read!/1
iex>


その意味合い上、感嘆符つきの関数があれば、感嘆符なしの同名の関数が存在している可能性があります。
なぜ、感嘆符つき関数が用意されているのかを確認してみると良いのかもしれません。

これまで、あまり例外を発生させるようなコードを書いていなかったので、必要になった際には感嘆符つきの関数を作成してみます。

*1:英語圏だと、a trailing bang が一般的?

*2:習慣的であるため、強制力はない様子

*3:2020/08/12時点の最新バージョンElixir 1.10.4で確認

*4:DeepLでの翻訳

*5:こちらでは、File.open!/2が取り上げられていた