Elixir: inspect/2関数について
Tags: elixir, webdev, tutorial, programming
Published: 2019-01-21 16:38:07

---
title: Elixir: inspect/2関数について
published: true
description: 
tags: #elixir, #webdev, #tutorial, #programming
---

本稿は「[inspect について調べてみた](https://qiita.com/melpon/items/dede48ad3bbc8c4ff443)」をもとに加筆・補正し、文章を整えました。

多くのElixirの開発者にとって[`inspect/2`](https://hexdocs.pm/elixir/Kernel.html#inspect/2)は「どんなtermでも文字列にしてくれる便利な関数」という認識でしょう。それはもちろん確かです。けれど、さらに調べてみると、奥が深いことに気づきます。

# 出力フォーマット
`inspect/2`は、デフォルトではただ文字列に変換するだけです。けれども、豊富なオプションが備わっています。[ドキュメント](https://hexdocs.pm/elixir/Kernel.html#inspect/2)には、オプションについては[`Inspect.Opts`](https://hexdocs.pm/elixir/Inspect.Opts.html)を参照とあるものの、あまり詳しくはありません。本稿では、役に立ちそうなオプションについて、ドキュメントに書かれていない点も補ってご説明します。網羅的ではありませんので、オプション一覧はドキュメントでお確かめください。

## `:limit`
タプル、ビットストリング、マップ、リストやその他コレクション全体で表示する要素数を定めます。文字列と文字リストには後述`:printable_limit`をお使いください。デフォルトは50個です。`:infinity`を与えると制限なく、すべて表示します 。

```elixir
# 要素数３個までしか表示しない
iex> inspect [1, 2, 3, 4, 5], limit: 3
"[1, 2, 3, ...]"
```
`inspect/2`で出力した要素が途中で`...`に隠れてデバッグできないという場合は、`:infinity`で確かめられるようになります。ただし、ログの量が激増して動作を遅くすることもありますので、注意してください。

また、`:limit`は単純な要素数の指定ではありません。要素を表示するたびに`:limit`はひとつずつ減らし、0以下になったら表示しないというロジックを再帰して実行します。たとえば、つぎの例はネストしたデータの場合です。`limit: 4`で6要素表示されていることがわかります[^1]。

```elixir
iex> inspect [{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}], limit: 4
"[{1, 2, 3}, {4, 5, ...}, {7, ...}, {...}]"
```


## `:printable_limit`
文字列や文字のリストを表示するコードポイント数を定めます[^2]。デフォルトは4096文字です。`:infinity`を与えると制限なく、すべて表示します。`:infinity`を使う場合の注意は、前述`:limit`と同じです。

```elixir
iex(3)> inspect ["123456789", "あいうえおかきく"], printable_limit: 5
"[\"12345\" <> ..., \"あいうえお\" <> ...]"
```

上記のふたつのオプションを合わせて用いれば、すべてのtermが出力されるので、覚えておくと便利でしょう。ただし、ログの量には注意してお使いください。

```elixir
def dump(value) do
  inspect value, limit: :infinity, printable_limit: :infinity
end
```


## `:width`
出力する横幅の指定で、デフォルトは80文字です。ただし、`inspect/2`関数の場合は`:pretty `が`true`のときしか効果がありません。逆に、`IO.inspect/2`関数の場合は、つねに`:pretty`を無視します。`:infinity`を与えると、横幅を気にせず表示します。

そのため、`IO.inspect/2`を使って`IO.puts "#{inspect value}"`と同じ表示にするなら、`IO.inspect value, width: :infinity`のように`:infinity`を指定するのがよいでしょう。

なお、0を定めた場合は、要素ごとに改行します。

```elixir
# inspect で :pretty を指定しない場合、:width は無視される
iex> inspect [1, 2, 3], width: 0              
"[1, 2, 3]"

# pretty: true にすると width: 0 が有効になる
iex> inspect [1, 2, 3], pretty: true, width: 0
"[1,\n 2,\n 3]"

# IO.inspect は pretty オプションを無視して常に pretty print する
iex> IO.inspect [1, 2, 3], pretty: false, width: 0
[1,
 2,
 3]
[1, 2, 3]
```


## `:binaries`
渡されたバイナリをどのように扱うかを定めます。文字列も単なるバイナリですので、`inspect/2`ではこのオプションを見てどのように表示するかを決めます。値はつぎの3つです。

- `:as_strings`: 文字列として出力され、表示できないバイトはエスケープされます。
- `:as_binaries`: ビット構文で出力されます。
- `:infer`(デフォルト): `String.printable?/2`が`true`の場合には文字列として、それ以外の場合はビット構文として出力されます。

```elixir
iex> inspect("olá")
"\"olá\""
iex> inspect("olá" <> <<0>>)
"<<111, 108, 195, 161, 0>>"
iex> inspect("olá" <> <<0>>, binaries: :as_strings)
"\"olá\\0\""
iex> inspect("olá", binaries: :as_binaries)
"<<111, 108, 195, 161>>"
```

## `:charlists`
渡されたリストの扱い方を定めます。文字のリストも単なるリストですので、`inspect/2`はこのオプションによりどう表示するかを決めます。値はつぎの3つです。

- `:as_charlists`: リストがすべて文字リストとして出力されます。
- `:as_lists`: すべてリストとして出力されます。
- `:infer`(デフォルト): `Inspect.List.printable?/2`が`true`の場合には文字のリストとして、それ以外の場合はリストとして出力されます。

実装を見るかぎり、アルファベットや空白文字(改行やスペース、タブ)以外、たとえば日本語が含まれている場合はリストとして表示されるようです。[Erlangに似た挙動](https://qiita.com/melpon/items/ff11acc6a64268f78a90#%E6%96%87%E5%AD%97%E5%88%97%E3%81%AF%E3%82%B3%E3%83%BC%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%AB%E3%81%AA%E3%82%8B)といえます。

```elixir
iex> inspect('bar')
"'bar'"
iex> inspect('barバー')
"[98, 97, 114, 12496, 12540]"
iex> inspect('barバー', charlists: :as_charlists)
"'barバー'"
iex> inspect('bar', charlists: :as_lists)
"[98, 97, 114]"
```

## `:syntax_colors`
出力する文字列のカラーを、キーワードリストで定められます。キーはタイプで、値が使う色です。タイプには、`:number`、`:atom`、`:regex`, `:tuple`、`:map`、`:list`、and `:reset`が標準で含まれています。

つぎのコードは、atomを`:cyan`、マップは`:magenta`で、数値を`:black`にしたうえで、背景色は`:light_blue_background`で出力します。

```elixir
iex> inspect %{x: 10, y: 20}, syntax_colors: [atom: :cyan, map: :magenta, number: [:black, :light_blue_background]]
"\e[35m%{\e[0m\e[36mx:\e[0m \e[30m\e[104m10\e[0m\e[35m,\e[0m \e[36my:\e[0m \e[30m\e[104m20\e[0m\e[35m}\e[0m"
```

色のエスケープコードは[ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code)です。ANSIエスケープコードに対応したコンソールに出力すると、指定した色で表示されます。

![qiita_1901_001_001.png](https://thepracticaldev.s3.amazonaws.com/i/thk9g6rs7h3cc12hqmbl.png)

`:syntax_colors`には、キーとして任意のアトムが渡せます。ただし、標準のキーしか使われません。その他のキーは、独自に拡張したときに利用可能です。

値には [`IO.ANSI`](https://hexdocs.pm/elixir/IO.ANSI.html)に定義されている色の関数名（アトム）か、任意の文字列、あるいはそれらのリスト（ネスト可）が指定できます。


# `Inspect`プロトコルについて
構造体をつくったとき、`inspect/2`の呼び出しによるデフォルト表示はつぎのようになります。前述のオプション、たとえば`:width`も使えます。このとき、`pretty: true`を加えることにご注意ください。

```elixir
defmodule MyStruct do
  defstruct [:x, :y]
end
```

```elixir
iex> IO.puts(inspect %MyStruct{x: 10, y: 20})
%MyStruct{x: 10, y: 20}
:ok
iex> IO.puts(inspect %MyStruct{x: 10, y: 20}, pretty: true, width: 0)
%MyStruct{
  x: 10,
  y: 20
}
:ok
```

この表示をカスタマイズしたい場合には、[`Inspect`](https://hexdocs.pm/elixir/Inspect.html)プロトコルを実装しなければなりません。


## `Inspect`プロトコルを実装する
[`inspect/2`](https://hexdocs.pm/elixir/Inspect.html#inspect/2)は、第1引数に構造体を受け取ります。その値を出力する文字列で表せば、独自のフォーマットが実装できます。

```elixir
defimpl Inspect, for: MyStruct do
  def inspect(term, _opts) do
    "(#{term.x}, #{term.y})"
  end
end
```

```elixir
iex> IO.puts(inspect %MyStruct{x: 10, y: 20})
(10, 20)
:ok
```


## 改行を適切に入れる
`Inspect`プロトコルで実装した独自のフォーマットに`:width`オプションを用いても、そのままでは幅が縮まりません。改行できる値の区切りは、情報として`inspect/2`の戻り値に含めなければならないのです。

```elixir
iex(2)> IO.puts(inspect %MyStruct{x: 10, y: 20}, pretty: true, width: 0)
(10, 20)
:ok
```

そこで、区切りやネストなどのメタ情報を加えて返すために使うのが[`Inspect.Algebra`](https://hexdocs.pm/elixir/Inspect.Algebra.html)です。`:width`や`:limit`といったオプションに対して適切に表示するには、[`Inspect.Algebra.container_doc/6`](https://hexdocs.pm/elixir/Inspect.Algebra.html#container_doc/6)を用います。

```elixir
defimpl Inspect, for: MyStruct do
  # def inspect(term, _opts) do
  def inspect(term, opts) do
    # "(#{term.x}, #{term.y})"
    Inspect.Algebra.container_doc("(", [term.x, term.y], ")", opts, &Inspect.inspect/2)
  end
end
```



```elixir
iex> IO.puts(inspect %MyStruct{x: 10, y: 20})
(10, 20)

# :widthを小さくする
iex> IO.puts(inspect %MyStruct{x: 10, y: 20}, pretty: true, width: 0)
(10,
 20)

# 構造体をネストする
iex> IO.puts(inspect %MyStruct{x: 10, y: %MyStruct{x: 100, y: 200}}, pretty: true, width: 0)
(
  10,
  (100,
   200)
)

# :limitを定める
iex> IO.puts(inspect %MyStruct{x: 10, y: %MyStruct{x: 100, y: 200}}, pretty: true, width: 0, limit: 2)
(
  10,
  (...)
)
```

横幅に応じて適切に改行が入り、しかもネストしたとき行頭の空白も正しく加えられました。


## 色をつける
[`Inspect.Algebra.color/3`](https://hexdocs.pm/elixir/Inspect.Algebra.html#color/3)で色をつけることもできます。第2引数に渡すカラーキー(`:my_struct`)は、`inspect/2`を呼び出すとき独自に定めて渡します。

```elixir
defimpl Inspect, for: MyStruct do
  def inspect(term, opts) do
    Inspect.Algebra.container_doc(
      Inspect.Algebra.color("(", :my_struct, opts),
      [term.x, term.y],
      Inspect.Algebra.color(")", :my_struct, opts),
      opts,
      &Inspect.inspect/2)
  end
end
```

つぎのコードは、`inspect/2`に`:syntax_colors`オプションでカラーキー`:my_struct`を[`:red_background`](https://hexdocs.pm/elixir/IO.ANSI.html#red_background/0)として渡しています。

```elixir
iex(2)> inspect %MyStruct{x: 10, y: 20}, syntax_colors: [my_struct: :red_background]
"\e[41m(\e[0m10, 20\e[41m)\e[0m"
```

コンソールに出力すると、指定した色で表示されます。

![qiita_1901_001_002.png](https://thepracticaldev.s3.amazonaws.com/i/fm8bpoe2ayrebll6k2tn.png)


# `IO.inspect/2`関数について
「出力フォーマット」の「:width」でも触れたように、`IO.puts(inspect value)`と`IO.inspect value`は同じ出力にはなりません。[`IO.inspect/2`](https://hexdocs.pm/elixir/IO.html#inspect/2)関数が異なるのはつぎの点です。

- `:pretty`オプションは無視します。
    - つねにpretty printです。
- `:label`オプションが使えます。
    - "my_label: value"のように表示されます。
- 与えられた値を返します。
    - パイプラインの途中で値が簡単に確かめられます。

ドキュメントの`IO.inspect/2`の項には、「[Examples](https://hexdocs.pm/elixir/IO.html#inspect/2-examples)」のひとつとしてつぎのようなコードが紹介されています。

```elixir
iex> [1, 2, 3] |> IO.inspect(label: "before") |> Enum.map(&(&1 * 2)) |> IO.inspect(label: "after") |> Enum.sum
before: [1, 2, 3]
after: [2, 4, 6]
12
```

[^1]: 要素を表示するたびに`:limit`がひとつずつ減り、0以下になったら表示しないという再帰処理の結果を示したのが以下の図です。ロジックを詳しく知りたい方は、[`Inspect.Algebra.container_doc/6`](https://hexdocs.pm/elixir/Inspect.Algebra.html#container_doc/6)の[実装](https://github.com/elixir-lang/elixir/blob/v1.8.0/lib/elixir/lib/inspect/algebra.ex#L347-L396)をご参照ください。  ![qiita_1901_001_003.png](https://thepracticaldev.s3.amazonaws.com/i/lehrhc4kxv9if1sjl8ky.png)

[^2]: ドキュメントの[`Inspect.Opts`](https://hexdocs.pm/elixir/Inspect.Opts.html) には、バイト数("number of bytes")と説明されています。けれど、[コード](https://github.com/elixir-lang/elixir/blob/v1.8.0/lib/elixir/lib/code/identifier.ex#L240-L246)をみるかぎり、コードポイントを数えているようです。ただし、UTF-8として無効な文字列であってもエスケープして表示しようとしますので、コードポイントというのも正確な表現ではありません。