構造体

cf. 15 構造体 – Structs – Elixir

構造体はマップの拡張だ。初期値、コンパイル時の保証、多態をもたらす、って書いてある。多態ってのはどういう意味だ?それから初期値があるのもなんだかなぁという気がする。
まあ、いい。例を見てみよう。構造体を定義するには、モジュールの中で defstruct/1 を使う。

defmodule User do

  defstruct name: "john", age: 27

end
^o^ > iex struct.exs
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> %User{}
%User{age: 27, name: "john"}
iex(2)> %User{name: "meg"}
%User{age: 27, name: "meg"}
iex(3)> is_map(%User{})
true

構造体は、用意しているフィールドが存在することをコンパイル時に保証する。逆に言うと、用意していないフィールドを使おうとするとエラーになる。

iex(4)> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "john"}
    (stdlib) :maps.update(:oops, :field, %User{age: 27, name: "john"})
             struct.exs:3: anonymous fn/2 in User.__struct__/1
    (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
             expanding struct: User.__struct__/1
             iex:4: (file)

構造体はマップの拡張なので、マップと同様のアクセスができる。

iex(4)> john = %User{}
%User{age: 27, name: "john"}
iex(5)> john.name
"john"
iex(6)> meg = %{john | name: "meg"}
%User{age: 27, name: "meg"}
iex(7)> %{meg | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "meg"}
    (stdlib) :maps.update(:oops, :field, %User{age: 27, name: "meg"})
    (stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1263: :lists.foldl/3

パターンマッチングでもよく用いられる。

iex(7)> %User{name: name} = john
%User{age: 27, name: "john"}
iex(8)> name
"john"
iex(9)> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

構造体はディクショナリではないので Dict モジュールからは使えない。

iex(9)> Dict.get(%User{}, :name)
** (UndefinedFunctionError) function User.get/3 is undefined or private
    User.get(%User{age: 27, name: "john"}, :name, nil)

モジュールのアトリビュート

cf. 14 モジュールのアトリビュート – Module attributes – Elixir

Elixir では、モジュールのアトリビュートは3つの目的に使われる。

  1. モジュールは注釈をつける
  2. 定数として利用
  3. コンパイルの際にモジュールの一時的な保管場所として利用

注釈

最もよく使われるのは2つ。これだけは覚えておこう。

  • @moduledoc – モジュールのドキュメント
  • @doc – このしるしの次にある関数やマクロのドキュメント

例えば、前に作った Math モジュールにドキュメントを追加してみよう。

defmodule Math do

  @moduledoc """
Provides math-related functions.

## Example

iex> Math.sum(1, 2)
3

"""

  @doc """
Calculates the sum of two numbers.
"""
  def sum(a, b) do
    a + b
  end

end

これをコンパイルしてから iex を立ち上げると、モジュールが読み込まれ(カレントディレクトリにあるコンパイル済みファイルは自動的に読み込まれるのを思い出すこと)、ドキュメントを見ることができる。

^o^ > elixirc math.ex

^o^ > iex
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> h Math
* Math

Provides math-related functions.

## Example

  iex> Math.sum(1, 2)
  3


iex(2)> h Math.sum
* def sum(a, b)

Calculates the sum of two numbers.

定数

Elixir ではしばしばアトリビュートを定数のように使う。

defmodule MyServer do

  @initial_state %{host: "127.0.0.1", port: 3456}

  IO.inspect @initial_state

end
^o^ > elixir myserver.exs
%{host: "127.0.0.1", port: 3456}

アトリビュートは関数の中でも読める。

defmodule MyServer do

  @my_data 14

  def first_data, do: @my_data

  @my_data 13

  def second_data, do: @my_data

end

IO.puts MyServer.first_data
IO.puts MyServer.second_data
^o^ > elixir myserver2.exs
14
13

一時的な貯蔵

なんか難しそうなので、パス。

alias, require, import

cf. 13 alias,requireとimpot – alias, require and import – Elixir

alias

alias はモジュールに別名をつける。例えば、数学に特化した特殊なリスト操作のモジュールを考えてみる。

defmodule Math do

  alias Math.List, as: List

end

この例では Math.List モジュールに List という別名をつけている。List へのアクセスはすべて Math.List へと展開される。オリジナルの List へは Elixir.List としてアクセスできる。

List.flatten              # => Math.List.flatten
ELixir.List.flatten       # => List.flatten
Elixir.Math.List.flatten  # => Math.List.flatten

alias はレキシカルスコープだ。あるモジュールの中で定義した alias はそのモジュールの中だけで有効。また、特定の関数の中だけで定義することも可能だ。

defmodule Math do

  def plus(a, b) do
    alias Math.List, as: List
    # ...
  end

  def minus(a, b) do
    # ...
  end

end

require

たとえば、Integer.is_odd/1 マクロを使おうという場合、Integer モジュールをプログラムに読み込まなければならない。それをするのが require ディレクティブだ。

iex(1)> Integer.is_oss(3)
** (UndefinedFunctionError) function Integer.is_oss/1 is undefined or private. Did you mean one of:

      * is_odd/1

    (elixir) Integer.is_oss(3)
iex(1)> require Integer
Integer
iex(2)> Integer.is_odd(3)
true

require もレキシカルスコープ。

import

ある別のモジュールの関数やマクロを何度も使いたとき、import すれば装飾名(モジュール名)をつけずに使うことができる。たとえば List モジュールの duplicate を何度も使いたいときはこうする:

iex(3)> import List, only: [duplicate: 2]
List
iex(4)> duplicate(:ok, 3)
[:ok, :ok, :ok]

この例では List モジュールから duplicate/2 だけを import している。only は省略可能だけど、つけることが推奨されている。only の代わりに except を使うこともできる。
only には :macros:functions を渡すこともできる。:macros を渡すと、モジュールのすべてのマクロを import する。
import もレキシカルスコープで、特定の関数内で import することもできる。

iex(3)> import List, only: [duplicate: 2]
List
iex(4)> duplicate(:ok, 3)
[:ok, :ok, :ok]