【Ruby】論理演算子（&&、||）の短絡評価を本質から理解する

# 1. はじめに

プログラムのソースコードで、しばしば以下のように`&&`で連結された処理を目にします。
```rb
user && user.authenticate(params[:session][:password])
```

この処理を読み解くと、「`user`が存在する時のみ、`user`の`authenticate`メソッドを実行する」と捉えられます。
これと同様に、`&&`と`||`は次のような使われ方をすることがあります。

```rb
条件式1 && 条件式2 #=> 条件式1が真の時だけ、条件式2を実行する
条件式1 || 条件式2 #=> 条件式1が偽の時だけ、条件式2を実行する
```

では、なぜこのような挙動をするのか、原理を説明することはできるでしょうか？
「短絡評価」という名前や、コードの実行結果だけを暗記していて、原理はよくわからない、なんてことはありませんか？

実際、[Rubyの公式ドキュメントの論理演算子の項目](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#and)でも、どのような動きをするかについては記載があるものの、原理については説明が省かれているため、「なんとなくそういうものだ」と認識してしまっている方も多いのではないかと思います。

この記事では、`&&`と`||`の挙動の原理を理解したいという方のために、論理演算の本質から丁寧に説明していきます。

# 2. そもそも論理演算とは

一旦プログラムのことは置いておくとして、`A && B`という式が表す本来の意味について考えてみましょう。
（※論理演算自体は理解しているという方は、【[3. 論理演算の短絡評価](#3-論理演算の短絡評価)】まで飛ばしてください。）

`&&`は「AND」、つまり日本語で言うと「かつ」を表す論理演算子です。
`||`は「OR」、つまり日本語で言うと「または」を表す論理演算子です。

`A && B`というときの`A`と`B`は、論理学では**命題**と呼ばれ、「真」または「偽」のいずれかの性質を持ちます。
命題が本当なら「真」、嘘なら「偽」であると言います。

**論理演算**とは、AとBという命題が与えられた時、「AかつB」や「AまたはB」などの真偽値を求めることを表します。

## 2-1. ANDの論理演算
### AとBの両方とも真である時
まず、ANDの論理演算について考えます。
具体的に、AとBに以下の命題を当てはめてみましょう。
- A: ニンテンドースイッチはゲーム機である
- B: サッカーはスポーツである

Aは本当であり、Bも本当であるので、「Aは真である」「Bは真である」と言えます。
ではこの時、「AかつB」はどうでしょうか。

「AかつB」を日本語の文章にすると、『ニンテンドースイッチはゲーム機であり、かつサッカーはスポーツである』という命題となるので、これは当然本当だと言えます。
よって、Aが真であり、Bも真であるなら、「AかつB」は真であると言えます。

### AとBのいずれかが偽である時
では次にAを少し変えて、以下のような命題について考えるとします。
- A: ニンテンドースイッチは食べ物である
- B: サッカーはスポーツである

この場合、「Aは偽である」「Bは真である」ということが言えます。
ここで「AかつB」を日本語の文章にすると、『ニンテンドースイッチは食べ物であり、かつサッカーはスポーツである』という命題となりますが、これは嘘なので、「AかつB」は偽であると言えます。

同様に考えると、Bのみが偽の場合や、AとBの両方が偽の場合も、「AかつB」は偽となることが分かります。

### AND演算の真偽値表
これらをまとめると、「A」「B」「AかつB」の真偽値は、以下のようになります。
|A|B|AかつB|
|-|-|-|
|真|真|真|
|真|偽|偽|
|偽|真|偽|
|偽|偽|偽|

## 2-2. ORの論理演算
### AとBの両方とも偽である時
次に、ORの論理演算について考えます。
ここでは、AとBの命題がいずれも偽であるとします。
- A: ニンテンドースイッチは食べ物である => 偽
- B: サッカーは乗り物である => 偽

「AまたはB」を日本語の文章にすると、『ニンテンドースイッチは食べ物であるか、またはサッカーは乗り物である』という命題となります。これは当然嘘です。
よって、Aが偽であり、Bも偽であるなら、「AまたはB」は偽であると言えます。

### AとBのいずれかが真である時
では次にAの命題だけを真にしてみます。
- A: ニンテンドースイッチはゲーム機である => 真
- B: サッカーは乗り物である => 偽

「AまたはB」を日本語の文章にすると、『ニンテンドースイッチはゲーム機であるか、またはサッカーは乗り物である』という命題となります。
「または」という日本語から、AかBの片方の命題のみ正しければ良いので、この場合「AまたはB」という命題は正しいと言えます。
よって、AかBのいずれか（もしくは両方）が真ならば、「AまたはB」は真となることが分かります。

### OR演算の真偽値表
これらをまとめると、「A」「B」「AまたはB」の真偽値は、以下のようになります。
|A|B|AまたはB|
|-|-|-|
|真|真|真|
|真|偽|真|
|偽|真|真|
|偽|偽|偽|

# 3. 論理演算の短絡評価
上記の真偽値表から、「AかつB」が真となるのは、AとBの両方ともが真である場合のみだということが分かりました。
つまり、もしAが偽であるなら、**Bを見るまでもなく当然に、**「AかつB」は偽であるということが決定します。

同様に、「AまたはB」が偽となるのは、AとBの両方ともが偽である場合のみです。
つまり、もしAが真であるなら、**Bを見るまでもなく当然に、**「AまたはB」は真であるということが決定します。

もうお分かりでしょうか。
Rubyの`&&`や`||`といった論理演算子では、上記の太字部分のロジックが取り入れられています。

## 3-1. 本来的な論理演算
前提として、Rubyでは以下のように真偽値が評価されます。
- `nil`または`false` → 偽（falsy）
- その他全てのオブジェクト → 真（truthy）

まずはAND演算について考えてみましょう。
```rb
result = 条件式1 && 条件式2
```

この論理演算は、条件式1の評価結果に応じて、以下のように処理されます。
- 条件式1の真偽値が"偽"の場合
→　**条件式2を評価するまでもなく当然に、**`条件式1 && 条件式2`の真偽値は"偽"となるため、条件式1のみを評価した時点で処理が打ち切られる。

- 条件式1の真偽値が"真"の場合
→　`条件式1 && 条件式2`の真偽値は確定しないため、続けて条件式2の評価が行われる。

OR演算の場合も同様です。
```rb
result = 条件式1 || 条件式2
```

- 条件式1の真偽値が"真"の場合
→　**条件式2を評価するまでもなく当然に、**`条件式1 || 条件式2`の真偽値は"真"となるため、条件式1のみを評価した時点で処理が打ち切られる。

- 条件式1の真偽値が"偽"の場合
→　`条件式1 || 条件式2`の真偽値は確定しないため、続けて条件式2の評価が行われる。

このように、一方の条件式の評価を省略して全体の真偽値を導き出す挙動が **「短絡評価」** です。

本来的には、このようにAとBの2つの条件式の真偽値を評価し、「AかつB」や「AまたはB」の真偽値を得る、というのが論理演算子の役割です。

## 3-2. 論理演算の転用

では、ここで冒頭のコードに戻ってみましょう。
```rb
user && user.authenticate(params[:session][:password])
```
これは`条件式1 && 条件式2`の形をしているものの、論理演算を行って真偽値を得ることを目的とした記述ではありません。
論理演算子が持つ短絡評価という性質に着目し、**「条件式1を満たす場合のみ条件式2を実行したい」という目的のために転用**したものと言えるでしょう。

このコードは、条件式1、すなわち`user`の値に応じて、以下のような動きになります。
- userが`nil`である場合
→条件式1は"偽"と評価され、全体の真偽値は当然"偽"となる。よって条件式2の実行はスキップされる。
- userがUserオブジェクトを指している場合
→条件式1は"真"と評価されるので、全体の真偽値はまだ定まらない。続けて条件式2の`user.authenticate(params[:session][:password])`が実行される。（さらに言うなら、条件式2の返り値を真偽値で評価した結果に応じて、全体の真偽値が定まる）

これで謎が解けましたね！

# 4. 最後に
ここまで見てきた通り、`&&`や`||`は論理演算を行う演算子です。

C言語などのように`&&`や`||`を実行したときに真偽値そのものが返る言語とは違って、Rubyでは以下のように、「最後に評価された値」が返ってきます。
```rb
a = false && 1           #=> false
b = nil && 1             #=> nil
c = 'hoge' && 3 + 5      #=> 8
d = [].empty? && 'fuga'  #=> "fuga"
```

この挙動に加えて、実際の使われ方が論理演算という目的から離れていることも相俟って、`&&`や`||`の原理の分かりにくさが生じているのではないかと思い、本記事の執筆に至りました。

この記事が論理演算子を使って書かれたコードへの理解を深める一助となれば幸いです。
ありがとうございました。