let、whereはなぜ必要か?【Haskellにおける局所的な変数】
この記事では、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)
関数というのも結局は式と同じです。apple
、orange
、goukei
という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