技術メモ

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

Elixir入門(第八章 モジュールと関数 その1)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
今回は若干気になってたモジュール(Modules)と関数(functions)。
今までなんとなく扱ってきましたが、きっちり読み解いて行きたいところ。

今までにもString.lengthとかモジュールを使用してきましたが、モジュールは「複数の関数の集合」ということになるそうです。
まぁそうですよねという感じではありますが、おそらくオブジェクト指向との違いが出てくるのはこの辺では。

defmoduleというマクロでモジュールを宣言し、defというマクロでモジュール内の関数を宣言するそうです。

iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3

コンパイル(Compilation)

「コードが長くなるからスクリプトファイルに書いてコンパイルしなさい」とのことで、その流れでしょう。
まずはmath.exを作成し、中を書いていく

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

保存したらそのディレクトリで対象ファイルをコンパイル

elixirc math.ex

Elixir.Math.beamというファイルが同ディレクトリで生成されたら、そのディレクトリでiexを実行。

PS > iex.bat
Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Math.sum(1, 2)
3

おお~、使えた✌('ω')

基本的にElixirのプロジェクトでは以下のようなディレクトリ構成になるそうです。(おそらくベストプラクティス)

  • ebin(コンパイル済みバイナリ)
  • lib(Elixirのコード、*.ex)
  • test(テストコード、*.exs)

実際にプロジェクトとかを作成していく際にはmixと呼ばれるビルドツールを使うっぽいですが、まずは学習ということで泥臭くやっていきましょう的な事も書かれています。
たしかにQiitaの記事とかでmixとか見た気がする・・・

Scripted mode

*.ex*.exsの挙動の違い。
基本的にどちらもElixirのスクリプトファイルとして認識され、文法なども同じものらしいですが、*.exsコンパイル済みバイナリが生成されず逐次コンパイルしていくファイルとなるそうです。
あくまで、逐次コンパイルなのでコンパイルはされてるんですが、メモリ上にしか残らないみたいです。
どうりでテストコードに用いられる訳ですね。

一応例。math.exsというファイル名で以下を作成。

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)

実行。

PS > elixir math.exs
3

実行できました。*.beamファイルは生成されていません。

関数(Named functions)

名あり関数とか有名関数とかで訳すのか?とも思いましたがまぁ関数でええでしょう。

モジュール内で関数を宣言するときはdef/2を使用し、外部から実行できないプライベート関数を宣言するときはdefp/2を使用します。
(defのことをmacroと言ってみたり、functionと言ってみたり、どっちやねん)

defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)    #=> 3と表示される
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)、エラーがでる

何の言及もありませんでしたが、elixirでは#がコメントになるっぽいです。
実行してみる。

PS > elixir function.exs
3
** (UndefinedFunctionError) function Math.do_sum/2 is undefined or private
    Math.do_sum(1, 2)
    function.exs:12: (file)
    (elixir) lib/code.ex:767: Code.require_file/2

関数はガードを使用した場合分け?や複数の句に対応してます。引数の個数や状態によって結果を変更するときに使用しそうです。

defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_integer(x) do
    false
  end
end

IO.puts Math.zero?(0)         #=> true
IO.puts Math.zero?(1)         #=> false
IO.puts Math.zero?([1, 2, 3]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0)       #=> ** (FunctionClauseError)

クエスチョンマークが関数名に入ってるのが気になりますが、真偽値を返す関数の命名規則っぽいです。
hexdocs.pm

どの関数にも当てはまらなければエラー発生。

一応defifと同様ワンラインで書けるそうです。

defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_integer(x), do: false
end

簡単なモジュールなら良いかもしれませんが、ちょっと複雑な処理などは極力複数ラインの書き方で対応しましょう。

ちょっと長いのでこの辺で一旦区切ります。