この記事の内容
この記事では、プログラミングを理解する上ですごく大事な概念なはずなのに、あんまりちゃんと教えてもらえない「評価値」と「副作用」について書きたいと思います。
この記事のターゲットはプログラミング初級者です。特に理屈でプログライミングを覚えたい人に見てもらいたいです。
なお、コードはJavaScriptで書いていますが、他の言語でも概念は同じです。
自分なりの言葉で説明するので、正確でないところがあるかもしれません。ご愛敬。
式
「評価値」と「副作用」を解説する前に**「式」**について知る必要があります。
式とは次のようなものです。
1 + 2
この式は「1」、「+」、「2」の3つの要素で構成されています。
このうち「+」を**「演算子」と言います。
また、「1」、「2」を「オペランド」**と言います。
この式を別の表現に書き換えるなら
add(1, 2)
です。
むしろ、「1 + 2」という書き方は「add(1, 2)」のシンタックスシュガー(意味は同じだが、直感的に書けるようした構文)と思った方が本質がつかめるかもしれません。
何が言いたいかというと、演算子(+)とは関数の種類であり、オペランド(1、2)はその関数の引数ととらえる事ができるという事です。
演算子には、
+
-
*
/
などの「算術演算子」や
=
の「代入演算子」
()
の「関数呼び出し演算子」など、いろいろな種類があります。
JavaScriptの演算子については、以下のサイトを見れば全て書かれているので、一度目を通して見てください。
JavaScriptで使われるいろいろな記号が、実は「演算子」である事が確認できると思います。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
ちなみに、「+」のように「オペランド(引数)」を2個とる演算子を**「2項演算子」**と言います。
**「1項演算子」や「3項演算子」**もあります。
演算子の例 | 演算子を使った式の例 | |
---|---|---|
1項演算子 | ++ | a++ |
2項演算子 | + | 1 + 2 |
3項演算子 | ? : | a ? 1 : -1 |
評価値
次に「評価値」について説明します。
プログラミングにおいて、式を実行する事を**「式を評価する」**といいます。
そして、式を評価するとその結果の値が決まり、その値が式の部分を置き換えるような動きをします。
例えば、以下の式
1 + 2
を評価すると、この式は「3」へと置き換わります。
そして、私はこの値の事を「評価した結果の値」の意味で**「評価値」**(※)と呼んでいます。
(※)一般的な言葉と思っていましたが、どうやら違うみたいです。この記事では評価値と呼ぶ事にしています。
ここまでは、特に違和感なく理解できるかと思います。
では、次に
a = 5
この式の「評価値」は何でしょうか?
この「=」は「代入演算子」というのですが、「代入演算子」が「右辺(5)を左辺(a)に代入する」という事以外は習っていない人が多いのでしょうか?
実はこの式の「評価値」は「5」になります。
「代入演算子」は、右辺の結果が「評価値」となるのです。
確認したい方は、「F12」※を押して開発者ツールを開き、コンソールから「a = 5」を実行してみてください。評価値として「5」が返ってくるはずです。
※Chromeの場合は「F12」、Safariの場合は「Cmd + Option + i」で開発者ツールが開けます。
副作用
次に「副作用」について説明します。
「副作用」という名前がどこからきたのかWikiで調べると
プログラミングにおいて、式の評価による作用には、主たる作用とそれ以外の副作用(side effect)とがある
と書いてありました。
この「主たる作用」が「評価値を得る」という事でしょう。それ以外の事なので「"副"作用」と呼ぶようです。
では、この式の「副作用」は何でしょうか?
a = 5
この式の評価値は「5」でした。それ以外の事なので、「右辺(5)を左辺(a)に代入する」という事が、この式の「副作用」となります。気分的にはこちらを主作用としても良さそうですが、規則性からするとこちらが「副」という事になります。
まとめると、
評価値 | 副作用 |
---|---|
5 | aに5を代入する |
という事になります。
では、この式
1 + 1
の「副作用」は何でしょうか?
実はこの式の「副作用」は「なし」となります。
まとめると、
評価値 | 副作用 |
---|---|
2 | なし |
という事になります。
演算子の優先順位と結合性
また、「演算子」には評価する順番があります。
例えばこの式ですが、
a = 1 + 1
この式の「=」と「+」はどちらが先に実行(評価)されるでしょうか?
以下表の「優先順位」列を確認してみてください。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
「+」は「=」より演算子として優先順位が高くなっています。
なので、コンピュータが実行する順番は
- 「1 + 1」を評価して「2」に置換える
- 「a = 2」を評価して「2」と置換える、その副作用として「aに2が代入」される。
となります。
では、同じ演算子だとどうでしょう?
例えば、
5 - 2 - 1
です。
同じ演算子の場合は、「結合性」というのが大事になってきます。
以下表の「減算」行の「結合性」を確認してみてください。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
「左から右」と書かれています。
なので、この場合
- 「5 - 2」を評価して「3」に置換える
- 「3 - 1」を評価して「2」と置換える
という事になります。これも感覚的に分かりますね。
では、
b = a = 1
これはどうでしょうか?
「=」の結合性は、「右から左」と書いてあります。
なので、この場合
- 「a = 1」を評価して「1」と置換える、その副作用として「aに1が代入」される。
- 「b = 1」を評価して「1」と置換える、その副作用として「bに1が代入」される。
です。
これは感覚的にわからないと思います。このような場合でも「ルール」を知っておく事で対応できるようになります。
関数やメソッドの「評価値」と「副作用」を意識する
「評価値」と「副作用」を理解すると、関数やメソッドを分類する事ができるようになります。
例えば、以下の3つの関数を見て下さい。
function funcA (a, b) {
return a + b
}
function funcB (obj, a, b) {
obj.m = a + b
}
function funcC (obj, a, b) {
return obj.m = a + b
}
これらの関数の「評価値」と「副作用」は次のようになります。
関数 | 評価値 | 副作用 |
---|---|---|
funcA | a + b | なし |
funcB | なし(undfined) | 「a + b」の結果をobj.mに代入する |
funcC | a + b | 「a + b」の結果をobj.mに代入する |
このように関数やメソッドには、評価値しか持たないもの、副作用だけを持つもの、両方を持つものがあります。
funcAのように、入力(引数)だけで出力(返り値)が決まり、プログラム内部の状態を変更しない関数を**「純粋関数」**と言ったりします。
純粋関数は一番理解しやすく、テストが用意でバグが起こりにくい関数となります。
funcBのようにプログラム内部の状態を変更するような関数を**「破壊的な関数」**と言ったりします。
破壊的な関数は、呼び出し側から入力と出力が分かりにくく、バグの元になりやすいので避けたいところですが実行効率とのトレードオフになる事が多いので、許容する事も多いです。
funcCは「評価値」と「副作用」が同時にある関数です。よほど分かりやすい関数の仕様でないならば避けるべきです。
※言語によっては例外機構がなく、返り値でエラー情報を返している関数がライブラリにもあったりしますが、「そういう時代もあったんだね。」という事です。
プログラミング言語によって「副作用」とどう付き合っていくかのアプローチが異なりますが、それぞれの言語設計者が工夫した結果です。新しい言語を覚えるときにそのあたりを意識する事でよりその言語を深く知る事ができるようになります。
まとめ
- 式: 演算子とオペランドで構成される
- 評価値: 式を実行(評価)した時にその式と置き換わる値(評価した結果の値)
- 副作用: 式を実行(評価)した時に起こる副次的な作用
プログラミングでは各演算子の「評価値」と「副作用」を分けて覚える事で、より深く理解する事ができるようになります。
また、コンピュータがどのような順番で命令を解釈するのかは「演算子の優先順位と結合性」を確認する事で確かめる事ができるようになります。
関数やメソッドの評価値、副作用を意識する事でより深くプログラミングがわかるようになります。
以上、最後まで読んでくれてありがとうございます。