(この記事は、「fukuoka.ex x ザキ研 Advent Calendar 2017」の1日目です)
#はじめに
この度,fukuoka.exキャストとして, 「fukuoka.ex x ザキ研 Advent Calendar 2017」に参加させていただきました,@hisawayと申します.よろしくお願いします.
私が目指すのは, Python でいうところのNumPy相当の機能を作ることです. 私自身はオブジェクト指向言語しか扱ったことがありませんので,私と同じようにオブジェクト指向言語に慣れ親しんだ人が関数型言語を始める参考記事になればいいかなと思っています.
#動作環境
Mac OS 10.13.4
Elixir 1.6.4 (compiled with OTP 20)
配列:リスト
基本知識
Elixirでは,配列に相当するものとして,「リスト」もしくは「タプル」
があります.要素には数値以外も含めることができます.詳細な仕様については本家のサイトを確認しましょう.
# リスト
[0, 1, 2]
# タプル
{0, 1, 2}
リストの特徴をざっくり挙げると
- 先頭への操作を行うと高速
- ランダムアクセスは遅い
行列演算をする際にランダムアクセスして計算を行うのはパッと思いつくもので外積があります.しかし,四則演算や内積計算などは行列の先頭から計算を行なって行くため,リストを配列として用いてライブラリを書いていきます.
head,tail
リストに hd
とつけると,先頭の要素を値として取得することができます.
また,リストにtl
とつけると,先頭以外の要素をリストとして取得できます.
iex(1)> a = [0, 1, 2]
[0, 1, 2]
iex(2)> hd a
0
iex(3)> tl a
[1,2]
パターンマッチング
リストのパターンマッチングは以下のような構文です.この構文は関数を設計する際に必要です.
iex(1)> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex(2)> head
1
iex(3)> tail
[2, 3]
リスト同士の計算
リストでは,要素に数値以外も含めることができるので,リストが数値だけで構成されていたとしても加算を行うことはできません.(++
はリストの連結を行う演算子なので間違えないようにしましょう)
iex(1)> a = [0]
[0]
iex(2)> b = [1]
[1]
iex(3)> a + b
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+([0], [1])
なので先頭の要素を取得して,加算します.
iex(1)> a = [1]
[1]
iex(2)> b = [2]
[2]
iex(3)> [(hd a) + (hd b)]
[3]
()をつけないとはじかれてしまいます.
上記が今後コードを書く上で基礎となります.
[非推奨] ループは再帰呼び出しで
リストの仕様上からfor
文を使うより,先頭から処理をさせた方がパフォーマンスがよさそうだということがわかったかと思います.for
を使わずにどのようにループをさせるかというと関数の再帰呼び出しをします.
参考:9 再帰 - Reccursion
再帰呼び出しを行うためには,ガード句や前述のパターンマッチングの知識が必要となります.なので,再帰呼び出しについては次回で触れつつ,実際に関数の設計を行なっていきます.
まとめ
Elixirのリストの仕様についてまとめました.
- リストは先頭から操作すると速い
- リストのループは再帰呼び出しでする
私のようなC系に慣れた人にとっては変な(?)仕様に見えてしまうかもですが,少しずつ慣れていきましょう.
明日は @Takeshi-Kogu さんの「ElixirでExcelのデータの処理をしてみた」 です。お楽しみに!
追記 2019/02/24
fukuoka.exではリストの操作(ループ)は再帰ではなく,Enum.map/reduceを推奨しています.
map/reduceメリット
- 可読性がよくなる
- パイプライン演算子が使いやすい
- 副次的な効果としてパイプライン演算子を使うために関数の設計規則が自然と統一される
- 並列プログラミング処理系「Hastega」の恩恵を受けられる(予定)
パフォーマンスにこだわりがなければ一旦思考の外に追い出しましょう.
もしこだわりたい方がいましたら,こちらをご覧ください.言語処理系レベルからの最適化に挑戦中です.
(追記終わり)