- ベースは関数型プログラミング。
- ただし"頑張りすぎない"。
- Python + Pandasでコードを書く場合を想定しています。
- たくさんの方針があると思いますが、良ければ参考にしてください。
関数型プログラミングとは?
- 純粋関数を土台としたプログラミングパラダイム。
- 純粋関数とは… [1]
- 1つの値だけを返す。
- 引数に基づいて戻り値を計算する。
- 既存の値を変更しない。
- 純粋関数とは… [1]
"なんちゃって関数型プログラミング"の方針
方針1: 小さくて分かりやすい関数をたくさん作る
- 例えばDataFrameへの操作なら、DataFrameを受け入れてDataFrameを返すような関数を作る。
- 特にpandasの操作は自分で関数化し、unittestで挙動を確認しながら実装する。(詳細は方針2で後述)
- unittestを書くのが簡単になる。
- 特定の入力に対して出力が一意に定まるようになるので、テストを容易に作れる。
- LLMの支援を受けやすい。
- LLMは小さな関数を作るのが得意。
- 「入力されたDataFrameのカラムAとカラムBを使ってDataFrameにカラムCを追加して」のような。
- プロンプトを考える人間の負荷も低い。
- LLMは小さな関数を作るのが得意。
- 共通化できる処理は積極的に共通化する。
- 共通したコードを使うことでメンテナンス性が向上する。
- 片方は直したけど、こっちでは直し忘れていた…ということを防げる。
- 例外的な対処が必要な場合は、明示的に条件分岐して処理する。
- 「この実験の時は実験条件○○が特殊だったから、この値に反映しないといけない…」
- 関数に実験名も入力として渡せば条件分岐できる。
- 「この実験の時は実験条件○○が特殊だったから、この値に反映しないといけない…」
- 実装が進むに連れて、多くの処理がただ関数を呼び出すだけで済むようになるので楽になっていく。
- 共通したコードを使うことでメンテナンス性が向上する。
方針2: できるかぎりテストを書く
- 研究のフェーズが進んでから間違いが見つかるより、目の前で見つかった方が時間は節約できる。
- unittestを書いているとコーディング時間が2倍になってしまうかもしれない…
- しかしコードの間違いに気が付かず、間違いの判明が後になればなるほど差し戻しの影響が大きくなる。
- 間違いの発見が研究のフェーズが後半になるほど、修正に掛かる時間も増えていく。
- ただし無理する必要も無い。
- テストを書くのにも時間がかかる。
- 時には一刻も早く結果だけを見たいときもある。
- 思ったよりも自分はPandasやPythonの挙動を把握できていない場合がある。
- 特にpandasのダウンサンプリング、アップサンプリングの挙動は難しい。
- またpandasは多数の構文を許容するので、間違った構文でも動いてしまう可能性がある。
- (そして間違った結果がDataFrameに格納されて気が付かない。)
- ライブラリのバージョンアップを容易に出来る。
- それぞれの関数にテストを付けてあれば、バージョンアップで挙動が変わらないことを保証できる。
- pandasの場合は構文が結構deprecatedになることがあり、テストがあると安心してバージョンアップできる。
- その関数内の書き方だけを修正すればプログラム全体に適用できる。
方針3: エラーハンドリングは頑張らない。エラーを見つけることに注力する。
- 入力するデータの質をコントロールできるので、例外処理で頑張らない。
- きっと入力元の"生データ"を生成したのも自分自身ではないだろうか。
- もし実行中に例外が発生したら? →そこでプログラムをクラッシュ/終了させる。
- 例えば: 生データのCSVをパースしたら想定よりもサンプル数が足りない。
- なぜそれ以上処理を続ける必要がある?
- 研究でのプログラムは安定稼働が求められるソフトウェアとは違い、正確な1つの結果が出てくることが優先である。
- 異常値に遭遇した際にはそこでクラッシュさせて直せばよい。
- ただ
raise Exception()
する。 - 上の層で例外をキャッチしない。
- 生データと自分のコードを見て、直すべき部分がどこか把握する。
- ただ
- どこを間違えたにせよ、対処しないと正しい実験結果は出せない。
- パース用の関数を修正する必要があるかもしれない。
- 計測機器の設定を間違えたのかもしれない。
- 計測方法自体が間違っているかもしれない。
- なぜそれ以上処理を続ける必要がある?
- 例えば: 生データのCSVをパースしたら想定よりもサンプル数が足りない。
- エラーハンドリングで頑張るのでは無く、エラーが起きる度に正していく。
- ただし、例外を発生させている時点で、純粋関数とは言えない。なので"なんちゃって関数型プログラミング"と呼んでいる。
方針4: Enum, dataclassを積極的に使う
- Enum
- 同レベルの値を識別するのにとても便利。
- 同モデルの計測機器A, B, C, …
- 計測者A, B, C, …
- 条件分岐がとてもシンプルになる。
- 同レベルの値を識別するのにとても便利。
- dataclass
- 少し複雑な値を型として扱えるようになる。
- 例えば、三次元座標はそれぞれの次元の座標を持っているべき ->
Coordinate(x=1, y=2, z=3)
- 例えば、三次元座標はそれぞれの次元の座標を持っているべき ->
- 型チェックの支援を受けられるようになる。
- 少し複雑な値を型として扱えるようになる。
なんちゃって関数型プログラミングをするのに便利なお供たち
- pandera
- DataFrameの構造や内容に対してバリデーションを実施できる。
- 例えば「このカラムは必ずint型のはずで、-100より大きくなることはない」、など。
- 時間の許す限りバリデーションを設置することでDataFrameが堅牢になる。
- Pylance
- Pythonの型チェックに使う。
- VSCodeの拡張機能の1つ。
- デフォルトだとほとんど警告してくれないので、より多くの警告を発するように変更する必要がある。
- 変更方法 -> TODO: 追記する
参考/引用した資料
- [1] なっとく!関数型プログラミング
- https://amzn.asia/d/0dzUEAAu
- 関数型プログラミングの考え方がとても分かりやすく書いてある。
- しかも読み物としても面白い。
- サンプルがScalaなのでPythonで再現できるところは限られてくるが、できるところまでやれば十分である。
- [2] Unixという考え方
- https://amzn.asia/d/05YA4XUB
- プログラムを作成する際の心構えがわかりやすく書いてある。
- 内容は古いが今でも問題なく採用できる方針が多数載っている。
- 賛否あるようだが自分は好き。