この記事は「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の16日目です。
昨日は@MzRyuKaさんの「elixir-desktopを起動しようとして環境構築でハマっている話」でした。
導入
さて、本日は「Enum.reduce
のデバッグにEnum.scan
が便利」というテーマです。
Enum
にはmap
やfilter
、reduce
といった、使用率の高い非常に便利な関数がありますが、reduce
はmap
やfilter
とは少し異なります。map
は受け取ったリストと同じサイズのリストを返し、filter
は受け取ったリストのサイズ以下のリストを返します。
# Enum.map
lst = [1,2,3,4,5]
Enum.map(lst, fn n -> n * 2 end)
# [2,4,6,8,10]
# Enum.filter
Enum.filter(lst, fn n -> n > 3 end)
# [4,5]
両者に共通していることは必ずリストを返すという点です。しかし、reduce
に関しては必ずしもリストを返さない関数です。
- リストの要素の合計値を求める
- リストの重複する要素を取り除く
- 出現する要素を集計する...
などなど、「リストから何かをする」というケースに非常に便利に用いることが出来ます。
lst = [1,2,3,4,5]
Enum.reduce(lst, 0, fn n, accum -> accum + n end)
# 15
reduceの難しい点🤔
便利なreduce
関数ですが、データの動き方には少し慣れが必要です。
基本的にはアキュムレーターと呼ばれる値の更新処理が重要となります。
リストから要素の合計を求めるという処理の場合は、初期値を0
の数値をアキュムレーターとして、数値同士の加算処理を行う必要があります。
fn n, accum -> accum + n end
次のアキュムレーターがどのような値になっているかを把握するのがreduce
の難点です。
普通にデバッグをすると以下のようになります(実際に過去に自分はこの方法でやっていました)。
lst = [1,2,3,4,5]
Enum.reduce(lst, 0, fn n, accum ->
next = accum + n
IO.inspect(next)
next
end)
# ※見やすさのために改行しています
# 1 3 6 10 15
第2引数の無名関数内部で、標準出力を行えばいいのですが、これでは非常に面倒です...
Enum.scanを使う🧪
Enum
で提供されているscan
がreduceのデバッグに非常に便利です。
まずはEnum.scan
の実行結果を見てみます。
lst = [1,2,3,4,5]
Enum.scan(lst, 0, fn n, accum -> accum + n end)
# [1,3,6,10,15]
どうでしょうか。先ほどのアキュムレーターの値の更新結果を確認することが出来ました。
リストの最大値を取得する処理でも見てみます。
lst = [3,1,4,5,2]
Enum.scan(lst, 0, fn n, acc ->
if n > acc do
n
else
acc
end
end)
# [3,3,4,5,5]
最大値がどのように推移しているのかが確認できました。reduce
の動作確認には非常に便利ですね。
ぜひご活用ください。
注意点としては、Enum.scan
はEnum.reduce
と異なり、最終的にはリストを返します。なので、デバッグが完了したら、必ずreduce
に戻しておく必要があります。
最後に
明日は「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の17日目は@pojiroさんです。
お楽しみに🙌