search
LoginSignup
2

More than 5 years have passed since last update.

posted at

updated at

プログラミング Elixir 第六章

概要

プログラミング Elixir

名前付き関数に関して解説している章。

calculator.exs
defmodule Calculator do
  def add_one(n) do
    n + 1
  end
end

上記の様に名前付き関数はモジュール内に定義する。
この関数は一つの引数を取り、add_one/1 という名前の関数である。

モジュールのコンパイル

iex に対して 2 通りの自作のモジュールをコンパイルする方法がある。

一つ目は iex を立ち上げる際に読み込む方法

$ iex calculator.exs

二つ目が iex 上で c ヘルパーで読み込む方法

iex> c "calculator.exs"
[Calculator]
iex> Calculator.add_one(2)
3
iex> Calculator.add_one("2")
** (ArithmeticError) bad argument in arithmetic expression
    calculator.exs:3: Calculator.add_one/1

上記の様にコンパイル後は自由に使うことができ、問題がある場合は例外を発生させて教えてくれる。

また、エラーでは add_one/1 と表示されているのがわがるが、Elixir では名前付き関数を名前とアリティ (arity, パラメーター数) で識別する。

関数のボディの記述方法

概要では do...end ブロックを使い、複数式をグループ化して記述しているが、do...end は基本的な構文ではなく、実際の構文は以下のようになる。

def add_one(n), do: n + 1

do: で複数行を記述する場合は () で囲む

def greet(greeting, name), do: (
  IO.puts greeting
  IO.puts "How're you doing, #{name}?"
)

do...end はただのシンタックスシュガーで、コンパイル時に do: 形式に変換される。
ただ、複数行に渡るときは通常 do...end を使う。

パターンマッチ

5 章で Elixir のパターンマッチによる強力な関数呼び出し方法を学んだが、名前付き関数でも同じように使うことができる。

違いは、名前付き関数の場合は毎回関数名の宣言をする必要があることだが、これによって関数をとても数学の定義近い形で表現することができる。

例えば階乗の定義は

factorial(0) -> 1
factorial(n) -> n * factorial(n - 1)

となるが、Elixir だと以下のように実装する

defmodule Factorial do
  def of(0), do: 1
  def of(n), do: n * of(n - 1)
end

0 が渡されるまで、パターンマッチで of(n) が呼び出され続けて、0 が渡されたタイミングで of(0) がマッチして 1 が返され、再帰が終了する。

iex> Factorial.of(3)
6
iex> Factorial.of(7)
5040
iex> Factorial.of(10)
3628800

このように、パターンマッチの特性により、とても自然に実装することができる。

この際、注意が必要なのはパターンマッチはコードの上から下に行われることである。そのため、以下の様に記述すると動かない。

factorial.exs
defmodule Factorial do
  def of(n), do: n * of(n - 1)
  def of(0), do: 1
end

このようなミスを無くすために、Elixir はコンパイル時に以下のような warning を出してくれる。

iex> c "factorial.exs"
warning: this clause cannot match because a previous clause at line 2 always matches
  factorial.exs:3

ガード節

パターンマッチの力は前回の無名関数でも見てきたが、動作を値の型や条件で区別したい場合にはマッチングだけでは不十分である。その際に使うのが when キーワードを使ったガード節である。

defmodule Guard do
  def what_is(x) when is_number(x) do
    IO.puts "#{x} is a number"
  end

  def what_is(x) when is_list(x) do
    IO.puts "#{inspect(x)} is a list"
  end

  def what_is(x) when is_atom(x) do
    IO.puts "#{x} is a atom"
  end
end
iex> Guard.what_is(99)
99 is a number
:ok
iex> Guard.what_is(:cat)
cat is a atom
:ok
iex> Guard.what_is([1, 2, 3])
[1, 2, 3] is a list
:ok

上記の階乗の実装を見直すと、負の値を渡したときに正しく動作しないことがわかる。
そこでガード節を使ってこの問題を解消する。

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0 do
    n * of(n - 1)
  end
end
iex> Factorial.of(-10)
** (FunctionClauseError) no function clause matching in Factorial.of/1
    iex:6: Factorial.of(-10)

ガード節で使える演算子はリファレンスにまとまっている

デフォルトパラメータ

デフォルトパラメータは (パラメータ) \\ (値) という形式で書く。

defmodule DefaultParameter do
  def test(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.puts "p1: #{p1}, p2: #{p2}, p3: #{p3}, p4: #{p4}"
  end
end

関数に渡された値は左のパラメータから束縛されていき、少ない分はデフォルトの値が使われる。
必須パラメータよりも引数が少なければエラーとなる。

iex> DefaultParameter.test("a", "b")
p1: a, p2: 2, p3: 3, p4: b
iex> DefaultParameter.test("a", "b", "c")
p1: a, p2: b, p3: 3, p4: c
iex> DefaultParameter.test("a", "b", "c", "d")
p1: a, p2: b, p3: c, p4: d
iex> DefaultParameter.test("a")
** (UndefinedFunctionError) function DefaultParameter.test/1 is undefined or private. Did you mean one of:

      * test/2
      * test/3
      * test/4

    DefaultParameter.test("a")

以下のような関数は、コンパイルするとエラーになる

defmodule DefaultParameter do
  def test(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.puts "p1: #{p1}, p2: #{p2}, p3: #{p3}, p4: #{p4}"
  end

  def test(p1, p2) do
    IO.puts "p1: #{p1}, p2: #{p2}"
  end
end
** (CompileError) iex:18: def test/2 conflicts with defaults from def test/4

最初の関数定義が引数 2, 3, 4 の場合、全てにマッチしてしまうためである。

複数のボディを持つ関数にデフォルト値を与える場合は、以下の様にボディなしのデフォルトパラメータを含む関数を最初に定義して、それ以降にボディの定義をしていく。

defmodule DefaultParameter do
  def test(p1, p2 \\ :default)

  def test(p1, p2) when is_list(p1) do
    IO.puts "p1 is list, p2: #{p2}"
  end

  def test(p1, p2) do
    IO.puts "p1: #{p1}, p2: #{p2}"
  end
end
iex> DefaultParameter.test("a")
p1: a, p2: default
iex> DefaultParameter.test("a", "b")
p1: a, p2: b
iex> DefaultParameter.test(["a"], "b")
p1 is list, p2: b

プライベート関数

プライベート関数は defp キーワードで定義する。プライベート関数は、定義されたモジュール内でしか呼び出せない。

defmodule PrivateSample do
  def func do
    IO.puts pfunc
  end

  defp pfunc do
    "private function"
  end
end
iex> PrivateSample.func
private function
iex> PrivateSample.pfunc
** (UndefinedFunctionError) function PrivateSample.pfunc/0 is undefined or private. Did you mean one of:

      * func/0

    PrivateSample.pfunc()

通常の関数と同様に複数のヘッドを持たせることができるが、あるヘッドをプライベートに、他をパブリックにということはできない。

defmodule PrivateSample do
  def pfunc(0) do
    IO.puts 0
  end

  defp pfunc(n) do
    IO.puts n
  end
end
== Compilation error on file sample.exs ==
** (CompileError) sample.exs:6: defp pfunc/1 already defined as def
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6

パイプ演算子

例えば他のプログラミング言語だと、以下のようなプログラムをよく書く

people = DB.find_customers
orders = Orders.for_customers(people)
tax    = sales_tax(orders, 2016)
filing = prepare_filing(tax)

これを「一度しか使わないのに変数に入れるのは嫌だ」となると以下のようなプログラムになる。

prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2016))

流石に、読みづらい上に、実行する順番が直観的でなく、難しい。
しかし、Elixir ならパイプ演算子 |> を使って、これを簡潔に書ける。

filing = DB.find_customers
           |> Orders.for_customers
           |> sales_tax(2016)
           |> prepare_filing

左の式の結果を、右の式の第一引数として渡して実行する。
つまり、val |> f(a, b)f(val, a, b) と同じ意味である。

上記の様に改行せずに一行で書くこともできる。

iex> (1..5) |> Enum.map(&(&1 + 1)) |> Enum.map(&(&1 * 2))
[4, 6, 8, 10, 12]

ただし、パイプ演算子を使って、パラメータ付きで関数を呼ぶ場合は括弧を使う。

このように、パイプ演算子を使えば、「これをやって、これをやって、、、」と思考の流れと近い形でプログラミングをすることができる。

とても強力な機能である。

モジュール

定義するものに、ネームスペースを提供する。

defmodule OuterModule do
  defmodule InnerModule do
    def func1 do
      IO.puts "func1 in InnerModule"
    end

    def func2 do
      func1
      IO.puts "func2 in InnerModule"
    end
  end

  def func1 do
    InnerModule.func1
    IO.puts "func1 in OuterModule"
  end
end
iex> OuterModule.func1
func1 in InnerModule
func1 in OuterModule
iex> OuterModule.InnerModule.func1
func1 in InnerModule
iex> OuterModule.InnerModule.func2
func1 in InnerModule
func2 in InnerModule

同じネームスペース内では、モジュール名を省略して呼び出すことができる。

モジュールは全てトップレベルで定義され、モジュール内にモジュールを定義すると Elixir はドットでつなげて定義する。
そのため、以下の様に定義することもできる。

defmodule OuterModule.InnerModule do
  def func3, do: "func3 in InnerModule"
end
iex> OuterModule.InnerModule.func3
"func3 in InnerModule"

ディレクティブ

モジュールには三種類のディレクティブが用意されている。

import

import Module [, only: | except: ]

モジュールの関数やマクロをカレントスコープに持ってくる。

例えば以下の例は String の split/2 関数を import している。

defmodule ImportSample do
  def split_by_space1(str) do
    String.split(str, " ")
  end

  def split_by_space2(str) do
    import String, only: [split: 2]

    split(str, " ")
  end
end
iex> ImportSample.split_by_space1("hoge fuga")
["hoge", "fuga"]
iex> ImportSample.split_by_space2("hoge fuga")
["hoge", "fuga"]

import してくることで、モジュール名を省略することができる。

only, except オプションは function_name: arity を渡すことで、import する関数を絞ることができる。また、:function, :macros を渡して、関数かマクロかを絞ることもできる。

alias

alias Module [, as:]

モジュールのエイリアスを作成して、モジュール名を省略することができる

defmodule Too.Long.Name.SampleModule do
  def func1 do
    "alias sample"
  end
end

defmodule AliasModule do
  def func1 do
    alias Too.Long.Name.SampleModule, as: SampleModule

    SampleModule.func1()
  end
end

SampleModule.func1() だけで呼び出せるようになるのがわかる。
また、デフォルトでモジュール名の最後の値をエイリアスにするので、上記のような場合 as: を省略できる。

alias Too.Long.Name.SampleModule

また、複数ある場合はタプルで渡せる。

alias Too.Long.Name.{SampleModule1, SampleModule2, SampleModule3}

require

require Module

モジュールで定義したマクロが使えるようになる。

属性

各モジュールはメタデータを持っており、それぞれをモジュールの属性という。
設定やメタデータとして使われる。

属性名には @ がついており、以下のように値をセットする。

@name value

値のセットはモジュールのトップレベルのみ可能で、関数からはアクセスのみ可能。

defmodule AttributeSample do
  @name "Yamada Taro"

  def user_name do
    @name
  end
end
iex> AttributeSample.user_name
"Yamada Taro"

関数内で値をセットするとエラーになる。

iex> defmodule AttributeSample do
...>   @name "Yamada Taro"
...>
...>   def set_user_name do
...>     @name "Yamada Jiro"
...>   end
...> end
warning: redefining module AttributeSample (current version defined in memory)
  iex:49

** (ArgumentError) cannot set attribute @name inside function/macro
    (elixir) lib/kernel.ex:2336: Kernel.do_at/4
    (elixir) expanding macro: Kernel.@/1
             iex:53: AttributeSample.set_user_name/0

値のセットは何度でも可能で、関数内で属性アクセスすると直前でセットされた値が使われる。

defmodule AttributeSample do
  @name "Yamada Taro"

  def user_name1 do
    @name 
  end

  @name "Yamada Jiro"

  def user_name2 do
    @name 
  end
end
iex> AttributeSample.user_name1
"Yamada Taro"
iex> AttributeSample.user_name2
"Yamada Jiro"

モジュールの名前について

Elixir は String.length("hoge") のように関数を呼び出すが、内部ではモジュール名をアトムに変換している。

iex> to_string String
"Elixir.String"
iex> :"Elixir.String"
String

そのため以下の様に呼び出すこともできる。

iex> :"Elixir.String".length("hoge")
4

Erlang の関数を呼び出す

Erlang は Elixir と違い、変数は大文字から始まり、アトムは小文字で命名される。そのため、Erlang の timer モジュールを使いたければ、timer をアトムにして呼べば良い。

iex> :timer.minutes(1)
60000

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2