関数型プログラミングで登場する用語、”参照透過性”、”副作用”、”純粋関数”を紹介します。
DRY原則は関数型プログラミング特有の用語ではないですが、大切な概念なので合わせて紹介します。
参照透過性
参照透過性は計算機言語の概念の一種です。簡略して説明すると以下の2つを抑えておけば良いです。
- 変数の値は不変
- 関数は同じ引数を与えられた場合、同じ値を返す
変数の値は不変
参照透過性であるためには変数の値が不変である必要があります。別の言い方をすると、変数を宣言して値を代入してもいいですが、初期値を代入した後は値を変えては行けません。
逆に変数が変わるケースは、代入と、変数の型によるものがあります。
代入
a = 1
b = a
result1 = (a==b) # (1)
a = 2
result2 = (a==b) # (2)
print(result1)
print(result2)
結果
True
False
(1)と(2)式の右辺は同じですが、結果が変わっています。この様に代入によって参照透過性が失われます。
型
リストやディクショナリーなど(ミュータブルな型)は変数の値が変わるため参照透過性がありません。
a = [1, 2]
print(a) # (1)
a.append(3)
print(a)
結果
[1,2]
[1,2,3]
(1)と(2)は同じコードですが、出力結果が異なります。これはappend関数によって配列の値を書き換えたからです。
関数は同じ引数を与えられた場合、同じ値を返す
x = 0
def add(y):
global x
x += 1
return x + y
result1 = add(1) # (1)
result2 = add(1) # (2)
print(result1)
print(result2)
結果
2
3
(1)と(2)の右辺は両方ともadd(1)ですが出力結果が異なります。
これはadd関数内部でグローバル変数に変更を加え、そのグローバル変数が、出力結果に影響を及ぼしているからです。
説明しやすい様にadd関数内でxに代入していますが、add関数内でxに代入を行わなくても他の関数が変数xの値を変更する場合もあります。
この様に引数が同じでも出力結果が異なる関数は参照透過ではありません。
副作用
副作用と聞くと、薬をイメージしませんか?薬の副作用とは期待される効果とは異なる作用(主に体調不良や、症状の悪化)の事です。
プログラミングの副作用も似ています。
プログラミングにおける副作用とは、ある機能がコンピュータの状態を変化させ、それ以降の結果に影響を与える作用です。
参照透過性の例として登場したadd関数は副作用を持つ機能の一つです。
他には以下の様な機能は副作用を持ちます
- 時間を返すDatetime.date()
- I/O制御(ファイルやDBへの書き込み読み込みなど)
- 例外を返す関数
ファイル入力の副作用
例としてファイル入力の副作用について説明します。
def read(file_path):
with open(file_path) as f:
txt = next(f)
print(txt)
file_path = '/sample.txt' #ファイルは必ず存在するとします
Read(file_path)
(1) sample.txtにhello, worldと書いてある場合の出力結果
hello, world
(2) sample.txtにhello, Japanと書いてある場合の出力結果
hello, Japan
sample.txtファイルの中身によってprint(txt)の結果が変わります。
open関数が副作用を持つため、それ以降の処理(この場合print(txt))に影響を及ぼしました。
Read関数も、副作用のあるopen関数を使っているため副作用のある関数です。
純粋関数
次の基準を満たす関数を、純粋関数と呼びます。
- 外部の状態に依存することがなく、関数の結果は入力のみに依存する
- 副作用を引き起こさない
- 関数自身のスコープの外にある値に影響を及ぼさない
不純な関数
純粋関数でない関数を不純な関数と言います。次の1または2を満たす関数です。
- 外部の状態に依存し、関数の結果に影響を与える
- 副作用を引き起こす
不純な関数の弊害
不純な関数は効率的ですが、開発者は変数の状態を追跡しなければいけません。プログラムのサイズが大きくなるにつれ、この変数の状態を追跡することが難しくなっていきます。
状態を回避することで概念的な規模を小さくし、大きな問題が解決しやすくなります。
例えば、クラスのメンバ変数はクラス内ならどこでも呼び出せるので便利ですが、変数の状態を追う必要が発生します。
DRY原則
DRY原則とは
“Don’t Repeat Yourself”
の略で、
”同じことを繰り返すな”
と言う原則です。
実際に開発現場で、ロジック(コードの一部)を色々な関数にコピペしているコードを何度も目にしたことがあります。
仮にこのロジックに修正が必要になった場合コピペした箇所全てを直さなければ行けません。10箇所にコピペしたら10箇所も修正が必要になります。
私は何度も苦い苦い経験を乗り越え、DRY原則の大切さを痛感しました。
設計にもよりますが、最低でも同じロジックが3箇所あったら関数にまとめてその関数を呼び出すようにしましょう。
私は同じロジックが2ヶ所あったら関数として抜き出せないか考えるようにしています。
似たようなロジックが現れた時も同様に、引数を上手く使ってメソッド化できないか考えてみましょう。関数に限らず時にはクラス設計やモジュールの構成を見直す必要も起こりえます。
抽象度の高いレイヤーから保守性が高く、機能追加に強いコードを設計できるようになるには日頃からコーディングスキルを磨く勉強や経験が必要です。
コメントを残す