(この記事は、「fukuoka.ex x ザキ研 Advent Calendar 2017」の8日目です)
昨日は@takasehidekiさんの「ElixirでIoT#1.3.2:プロセッサコアの動作周波数を揃えて比較評価してみた」でした.
#はじめに
前回の記事「Elixirのリストの仕様をまとめてみた」では,以下のようにリストの仕様をまとめました.
- リストは先頭から操作すると速い
- リストのループは再帰呼び出しでする
この記事では,Elixirで再帰呼び出しを使い,リストの操作を行う方法について説明します.
追記 2019/02/24
fukuoka.exではリストの操作(ループ)に再帰ではなく,Enum.map/reduceを推奨しています.
map/reduceメリット
- 可読性がよくなる
- パイプライン演算子が使いやすい
- 副次的な効果としてパイプライン演算子を使うために関数の設計規則が自然と統一される
- 並列プログラミング処理系「Hastega」の恩恵を受けられる(予定)
読み進める方は上記のメリットを踏まえて,なお再帰呼び出しをしなければならないときに参考にしていただければと思います.
関数呼び出しの条件
Elixirでは,リストでなくとも,再帰呼び出しでループを行います.
詳細は,9 再帰 - Reccursionを参考にしてください.
一般的な再帰呼び出しの例を示します.以下は,与えられた数の階乗を計算する関数です.
int factorial(int num){
if(num > 1)
return num * factorial(num-1);
else
return 1;
};
次に,Elixirで記述した例を示します.
defmodule Test do
def factorial(num) when num > 1 do
num * factorial(num-1)
end
def factorial(num), do: 1
end
(Elixirでは,関数が1文で済む場合は,do:
を使って記述できます.後に空白が必要です.)
比較すると以下のような違いがあります.
-
if
,else
のような制御文when
が関数名の後ろについている - 関数が再定義,オーバーライドされているように見える
when
のように条件を指定,拡張するものをガード句といいます.これもパターンマッチングの1つとなります.呼び出す条件を変えた関数を複数定義し,呼び出すことで再帰呼び出しを行います.
上記のようにElixirでは,関数を定義する際に呼び出す条件を設定することができます.
条件を設定する方法としては,ガード句の他に引数にパターンマッチングを使用する方法があります.
引数のパターンマッチ
引数がリストであるなら,変数に[ ]
をつける,hd
とhl
を使うなどができるため,is_list
のようなガード句は省略できます.一気に処理内容も書いてしまいます.
defmodule Test do
# 引数がリストである
def add([a_hd | a_tl], [b_hd | b_tl]) do
[ a_hd + b_hd | add(a_tl, b_tl) ]
end
# 引数がリストで,空である
def add([], []), do:[]
end
引数にパターンマッチングを使用したことで,関数の中で(hd a)
と書かずに引数として与えられたa_hd
をそのまま使用できます.また,空リスト[ ]
にhd
やtl
を使用するとエラーになりますので,add([],[])
と呼び出した場合にも対応させています.
完成?
これでリストの加算関数が完成...ではありません.
まだ1次元リストにしか,対応していません.
iex(1)> Test.add([1, 2, 3], [4, 5, 6])
[5, 7, 9]
iex(2)> Test.add([[1, 2], [3, 4]], [[5, 6], [7, 8]])
** (ArithmeticError) bad argument in arithmetic expression
(test) lib/test.ex:29: Test.add/2
NumPy相当というからには最低でも2次元,できれば3次元にも対応したいところです.
2次元リストはガード句と合わせて,実装する必要があるため,次回の記事で扱いたいと思います.
まとめ
以上で,1次元リストの加算関数を実装しました.
要点をまとましょう.
- Elixirでは,関数の呼び出し条件を設定できる
- 関数の引数にもパターンマッチングを使用できる
明日は @Takeshi-Kogu さんの「Elixirでファイルの入出力をしてみた」 です。お楽しみに!