Help us understand the problem. What is going on with this article?

Elixirのボディレス句(bodyless clause)について

More than 3 years have passed since last update.

Elixirでは、一つの関数に対して複数の句を定義することが出来ますが、Ecto.Changesetモジュールから抽出した下記の例の一番目の句のように、do部分が存在しない「ボディレス句(bodyless clause」を定義することが出来ます。

# 実装(doの部分)が存在しない
def cast(model_or_changeset, params, required, optional)

def cast(_model, %{__struct__: _} = params, _required, _optional) do
  〜省略〜
end

def cast(%Changeset{changes: changes, model: model} = changeset, params, required, optional) do
  〜省略〜
end

bodyless clauseは単体でも定義可能ですが、呼び出すことは出来ません。実装の存在する句と一緒に使われることが前提のようです。

このbodyless clauseがどういう用途で使われるのかということに関してですが、下記の記事によると3種類の用法があるようです。

Notes on Elixir: Bodyless Function Clauses

1. ドキュメンテーション用

例えば下記のような関数が定義されていた場合に、この関数をExDocを使ってドキュメント化すると、ドキュメントに記載される引数名には自動的にhash_dictという名称が使用されます。

def size(%HashDict{size: size}) do
  size
end

ドキュメント化される変数名を変更したい場合、下記のようなbodyless clauseを定義すると、ドキュメント化される変数名をdictにすることが出来ます。これが一つ目の用法になります。

def size(dict)

def size(%HashDict{size: size}) do
  size
end

2. デフォルト引数用

デフォルト引数をとる関数に複数の句を定義したい場合、bodyless clauseを使う必要があるようです。

こちらからのコピペですが、下記のような用法になります。

defmodule Concat do
  def join(a, b \\ nil, sep \\ " ")

  def join(a, b, _sep) when is_nil(b) do
    a
  end

  def join(a, b, sep) do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello")               #=> Hello

3. プロトコル用

3番目の用法はプロトコルです。当然といえば当然ですが、プロトコル定義には実装が存在しませんので、先日投稿した記事で解説させて頂いたex_adminの認証用のプロトコルだと下記のような感じでbodyless clauseが使用されています。

defprotocol ExAdmin.Authentication do
  @fallback_to_any true
  def use_authentication?(conn)
  def current_user(conn)
  def current_user_name(conn)
  def session_path(conn, action)
end

おまけ

下記のコードのパターンマッチ部分の%{__struct__: _} = paramsに関して、何をやってるんだろうと思った方がいるかもしれません。(私は思いました)

def cast(_model, %{__struct__: _} = params, _required, _optional) do
  〜省略〜
end

こちらは要するに「構造体かどうか」をチェックしているコードのようです。下記で説明されているように、構造体は実際には「__struct__」という特別なフィールドを持っているMapなので、上記の構文にマッチすれば構造体だと判断する、ということのようです。

Structs are bare maps underneath

ガード節では駄目なのだろうか?という疑問が当然湧いてきますが、Elixirにはis_listis_tupleis_mapのように「リストかどうか/タプルかどうか/マップかどうか」をチェックする関数はあってもis_structという関数は存在しないので、上記のようにチェックせざるを得ないようです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away