読者です 読者をやめる 読者になる 読者になる

もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

Elixir 入門 その19 - Typespecs and behaviours

Types and specs

  • ElixirはTypespecを使い以下の目的に用いる
    • カスタムデータ型を定義するとき
    • 関数のシグネチャ(仕様)を定義するとき
  • Typespecは開発者のためだけのものではない。例えばErlangの静的解析ツールdialyzer は typespecディレクティブを参照する。

Function specifications

# specの左辺に関数名と引数を、右辺には関数の戻り値の基本型を書く
# listを返す場合は [integer] のようにする。tupleは{:atom, integer} など。
@spec round(number) :: integer
def round(number), do: # implementation...

Defining custom types

  • @type を使うとビルトインの基本型の他にカスタムの型を定義することができる
  • スコープをprivateにしたいときは @typep を使うとよい
defmodule LousyCalculator do

  # カスタム型の説明と定義
  @typedoc """
  Just a number followed by a string.
  """
  @type number_with_offense :: {number, String.t}

  # returnにカスタム型を指定できる
  @spec add(number, number) :: number_with_offense
  def add(x, y), do: {x + y, "You need a calculator to do that?!"}

  @spec multiply(number, number) :: number_with_offense
  def multiply(x, y), do: {x * y, "Jeez, come on!"}
end
  • exportも可能
defmodule PoliteCalculator do
  @spec add(number, number) :: number
  def add(x, y), do: make_polite(LousyCalculator.add(x, y))

  # 引数の型としてカスタム型を指定している
  @spec make_polite(LousyCalculator.number_with_offense) :: number
  defp make_polite({num, _offense}), do: num
end

Behaviours

  • 多くのモジュールはAPIを共有している。例えばPlugの個々のモジュールにはinit/1とcall/2が実装されている。
  • Behaviourは以下の目的のために定義される。Javaにおけるinterfaceのようなもの。
    • モジュールによって実装しなければいけない関数を定義する
    • 必要な関数がすべて実装されていることを保証する

Defining behaviours

  • 例えばJSONYAMLのParserを書きたいとき、Parserとしては同じ動きで扱うものが違うだけなので共通のインタフェースを作ったほうがよい。まずはParserというbehaviourを定義する
defmodule Parser do
  # behaviour をrequireして __using__ を呼び出して初期設定を行う
  use Behaviour

  # defcallbackで実装しなければいけない関数を定義。
  # defcallbackで定義した関数はbehaviourを実装したモジュール内ですべて定義する必要がある
  defcallback parse(String.t) :: any
  defcallback extensions() :: [String.t]
end
defmodule JSONParser do
  @behaviour Parser

  def parse(str), do: # ... parse JSON
  def extensions, do: ["json"]
end

defmodule YAMLParser do
  @behaviour Parser

  def parse(str), do: # ... parse YAML
  def extensions, do: ["yml"]
end