🍡

let、whereはなぜ必要か?【Haskellにおける局所的な変数】

2022/04/26に公開

この記事では、let、whereの使い方について学びます。2つは局所的(その場限り)な変数ですが、どのようなメリットがあるのでしょうか。

通常の変数

まずは通常の変数から確認します。こちらはnumへ10を代入し、出力にかけています。

num :: Int
num = 10
main = print num

型宣言::によってデータ型を指定するのは少し特殊ですが、他の言語とキホンは同じですよね。

let、whereの定義

通常の変数とは別に、Haskellでは特殊な変数が2つ用意されています。let、whereとは局所的に(その場限りで)変数を使うための仕組みです。 主に以下の違いがあります。

2つの定義はこちらです。

■let~in:式の中だけで一時的な変数を定義

let 一時的な変数 = 〇〇
in (先ほどの一時的な変数のある)式

■where節:それが修飾する直前の関数で有効な変数を定義

関数 = (後で作る一時的な変数を含む)定義 
    where
      一時的な変数 = 〇〇
     一時的な関数 = 〇〇(whereは関数も定義可能です。)

なぜlet、whereは必要?

なぜその場限りで変数を宣言するのでしょうか。それはコードを読み書きしやすくするためです。関数の独立性を高めて目の前の1行に集中するという目的があります。具体的には以下の3つのメリットがあります。

1、目の前の1行に集中できる!
2、部品としてモジュール化できる!
3、ロジック部、変数の宣言部の順番を前後できる!

C言語の場合

局所的な変数という考えは、他の言語でもあります。例えばC言語でも、関数が違えば同じ名前の変数を宣言できますよね。ここではnumという名前の変数が、ソースコード内に2つあります。しかし関数が違うのでこれはアリです。

int twice (int num){
    printf(num * 2);
}

int half (int num){
    printf(num / 2)+
}

関数が違えば独立した部品として扱われます。なのでプログラム内に同じ名前の変数があっても、関数が違ってもOKとしよう..という考えです。

関数が違えば、違う変数

そもそも同じ名前を1度しか使えなかったら地獄ですよね。num1~1000まで増えてしまいそうです。グローバル変数だけでコードを書くのはあまりにツラ過ぎます。なので関数が違えば、全く別モノの変数として扱われます。名前が同じでもアリです。

それはHaskellでも同じです。特にHaskellは関数を組み合わせてをプログラを作ります。関数を独立した部品として扱うため、関数が違えば変数を別モノとする仕組みを用意しています。

変数の破棄、独立性

また変数の破棄、独立性を高めることは、読み書きする際にも大きな利点となります。

コードを読み書きする際には、目の前の1行に集中できるのがベストです。はるか100行前にある関数の影響を考えながら読むのはツラいですよね。そこで関数を読む際には、できるだけ目の前のコードだけに集中できるようにします。

モジュールの独立性・可読性に加えて、let、whereにはもう1つ機能があります。変数の宣言部の順番を前後させることができる点です。 通常の言語では多くの場合、変数→処理という順番で関数を書きますよね。材料としての変数を用意してから、それを実際に処理していく..というイメージです。ですがwhereを使えば、処理内容を先に書いて、後で変数を宣言できます。これによって関数の見通しが非常に良くなります。

let〜in

letとは、その式の中だけで有効な変数を作る仕組みです。 ここではリンゴとオレンジの合計金額が3,000円を超えるか?を判定しています。

main :: IO()
keisan :: Int -> Int -> String
keisan x y =
    let apple = 200
        orange = 300
        goukei = (x * apple) + (y * orange)
    in if goukei <= 3000
        then "予算以内です"
        else "予算オーバーです"

main = putStrLn (keisan 5 6)

関数というのも結局は式と同じです。appleorangegoukeiという3つの変数はkeisan関数の中だけでしか使うことができません。

where

whereとは、それが修飾する直前の関数で有効な変数を定義する仕組みです。 where節で定義した変数は、その直前にあるコードへ適用されるようになります。

先ほどの判定プログラムをwhereを使って書き直してみます。

main :: IO()
keisan :: Int -> Int -> String
main = putStrLn (keisan 5 6)
keisan x y =
    if goukei <= 3000
        then "予算以内です"
        else "予算オーバーです"
    where apple = 200
          orange = 300
          goukei = (x * apple) + (y * orange)

「何をしたいのか?」という結論を先に持ってくることができました。whereを使うことで、変数の宣言をダラダラと書く部分を後回しにできます。またこれらの変数は関数の中でのみ使われ、このブロックを抜けると使うことはできません。

let、whereは何が違うの?

ここまでの説明だと「順番が前か?後か?」という違いしかないように感じます。しかし2つの最大の違いは「式か?節か?」という違いにあります。

let:式(=値である)
where:節(=文である)

ここから2つの違いを導くことができます。

1、let式はそれ自体が値を持つ。whereは節なので値を持たない。
2、wher節は複数のガードにまたがって有効。letはその式の中だけ

letは式なので、コードの中でどんな場所でも使えます。

goukei = 10 * (let num = 10 in num + 2)
main = print goukei

しかし変数の範囲が局所的なので、(同じ関数の中であっても)ガードをまたいで使うことはできません。また後ろで変数を定義した方がわかりやすい場合も多いので、場面に応じて使うと良いのではないかと思います。

まとめ

「変数を宣言する」といっても、Haskellには3つの方法があります。

1、通常の宣言方法
2、let~in(式の中だけで有効)
3、where(変数の定義を後回し)

■let~in:式の中だけで一時的な変数を定義

let 一時的な変数 = 〇〇
in (先ほどの一時的な変数のある)式

■where節:それが修飾する直前の関数で有効な変数を定義

関数 = (後で作る一時的な変数を含む)定義 
    where
      一時的な変数 = 〇〇
     一時的な関数 = 〇〇(whereは関数も定義可能です。)

場面に応じてそれぞれを使い分けできるようにしておきましょう。

Discussion