Haskell:Let vs. Let


117

私はHaskellを初めて使用するので、WhereLetで非常に混乱しています。どちらも同じような目的を提供しているようです。WhereLetの比較をいくつか読んだことがありますが、それぞれをいつ使用するかを見分けるのに問題があります。誰かがコンテキストを提供したり、場合によってはどちらを使用するかを示すいくつかの例を提供してもらえますか?

どこでvs. Let

where句は、関数定義のレベルで定義することができます。通常、これはlet定義の範囲と同じです。唯一の違いは、ガードが使用されている場合です。where条項の範囲はすべてのガードに及びます。対照的に、let式のスコープは、現在の関数句とガード(存在する場合)のみです。

Haskellチートシート

Haskellのウィキは非常に詳細であり、様々な例を提供していますが、仮想的な例を使用しています。初心者には説明が短すぎます。

Letの利点

f :: State s a
f = State $ \x -> y
   where y = ... x ...

Control.Monad.State

whereはパターンマッチングf =を参照するため、機能しません。xはスコープ内にありません。対照的に、letから始めていれば、問題はありません。

Letの利点に関するHaskell Wiki

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

場所の利点

f x
  | cond1 x   = a
  | cond2 x   = g a
  | otherwise = f (h x a)
  where
    a = w x

f x
  = let a = w x
    in case () of
        _ | cond1 x   = a
          | cond2 x   = g a
          | otherwise = f (h x a)

宣言と表現

Haskell wikiは、Let式は表現力豊かであるのに対し、Where句は宣言的であることを述べています。スタイルとは別に、彼らはどのように異なるパフォーマンスをしますか?

Declaration style                     | Expression-style
--------------------------------------+---------------------------------------------
where clause                          | let expression
arguments LHS:     f x = x*x          | Lambda abstraction: f = \x -> x*x
Pattern matching:  f [] = 0           | case expression:    f xs = case xs of [] -> 0
Guards:            f [x] | x>0 = 'a'  | if expression:      f [x] = if x>0 then 'a' else ...
  1. 最初の例では、なぜLetがスコープにあるのに、どこにないのですか?
  2. 最初の例にWhereを適用することは可能ですか?
  3. 変数が実際の式を表す実際の例にこれを適用できる人はいますか?
  4. それぞれを使用するときに従うべき一般的な経験則はありますか?

更新

後でこのスレッドから来る人のために、私はここで見つけるべき最良の説明を見つけました:「Haskellへの穏やかな紹介」。

式をしましょう。

Haskellのlet式は、バインドのネストされたセットが必要なときにいつでも役立ちます。簡単な例として、次のことを考慮してください。

let y   = a*b
    f x = (x+y)/y
in f c + f d

let式によって作成されたバインディングのセットは相互に再帰的であり、パターンバインディングは遅延パターンとして扱われます(つまり、暗黙の〜が含まれます)。許可される宣言の種類は、型シグネチャ、関数バインディング、およびパターンバインディングのみです。

Where句。

バインディングをいくつかの保護された方程式にスコープすることが便利な場合があります。これにはwhere句が必要です。

f x y  |  y>z           =  ...
       |  y==z          =  ...
       |  y<z           =  ...
     where z = x*x

これはlet式では実行できないことに注意してください。これは、それが囲む式のみをスコープとしています。where句は、一連の方程式またはケース式の最上位でのみ使用できます。let式のバインディングに関する同じプロパティと制約は、where句のバインディングにも適用されます。ネストされたスコープのこれら2つの形式は非常によく似ていますが、let式は式ですが、where句はそうではありません。これは、関数宣言とcase式の構文の一部です。


9
私は違いによる戸惑いましたletし、where私が最初にHaskellのを学び始めました。それを理解するための最良の方法は、両者の違いがほとんどないことを認識することです。したがって、心配する必要はありません。where与えられた の意味はlet、非常に単純な機械的変換によるものです。haskell.org/onlinereport/decls.html#sect4.4.3.2を参照してください この変換は、表記上の便宜のためにのみ存在します。
トム・エリス

通常、最初に定義する内容に応じて、どちらか一方を使用します。たとえば、人々はしばしば関数を使用し、どこでそれらを定義します。Letは、ある種の命令的な機能が必要な場合に使用されます。
PyRulez 2013

@トム・エリス、トム、あなたが参照しているリンクを理解しようとしていましたが、私には難しすぎました。この単純な変身を単なる人間に変えることを説明できますか?
jhegedus 2015

1
@jhegedus:f = body where x = xbody; y = ybody ...手段f = let x = xbody; y = ybody ... in body
トム・エリス

トムありがとう!それは逆にできますか?case .... of ... whereどういうわけかlet式を式に変換することは可能ですか?これについてはよくわかりません。
jhegedus

回答:


39

1:例の問題

f :: State s a
f = State $ \x -> y
    where y = ... x ...

パラメータxです。where句内のものは、関数のパラメーターf(存在しない)と外部スコープ内のもののみを参照できます。

2:where最初の例でaを使用するには、次のxように、パラメーターとしてを受け取る2番目の名前付き関数を導入できます。

f = State f'
f' x = y
    where y = ... x ...

またはこのように:

f = State f'
    where
    f' x = y
        where y = ... x ...

3:以下はのない完全な例...です。

module StateExample where

data State a s = State (s -> (a, s))

f1 :: State Int (Int, Int)
f1 = State $ \state@(a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f state@(a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4:いつ使用するletwhere、または好みの問題です。let(計算を前に移動することによって)計算を強調し、(where計算を後ろに移動することによって)プログラムフローを強調するために使用します。


2
「where句の内容は、関数fのパラメーター(存在しない)と外側のスコープのパラメーターのみを参照できます。」-それは本当に私にとってそれを明確にするのに役立ちます。

28

ephemientが指摘したガードに関しては技術的な違いがありますが、以下に定義されている追加の変数を使用してメインの式を前に置くか、whereまたはすべてを前もって定義して式を置くかという概念的な違いもあります以下(let)。それぞれのスタイルの強調は異なり、数学の論文や教科書などで両方が使用されています。一般に、直感的でない変数は、上で定義しないと式が意味をなさないため、上で定義する必要があります。コンテキストまたはその名前のために直感的な変数は、以下で定義する必要があります。たとえば、ephemientのhasVowelの例では、の意味vowelsは明白であるため、その使用法を超えて定義する必要はありません(letガードのために機能しないという事実を無視して)。


1
これは大まかな経験則です。letのスコープが場所とは異なるように見える理由を詳しく説明してください。

Haskellの構文がそう言っているからです。申し訳ありません、良い答えはありません。「let」の下に隠れていると、トップスコープの定義が読みにくくなる可能性があるため、許可されませんでした。
gdj

13

法的:

main = print (1 + (let i = 10 in 2 * i + 1))

違法:

main = print (1 + (2 * i + 1 where i = 10))

法的:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

非合法:(MLとは異なり)

let vowels = "AEIOUaeiou"
in hasVowel = ...

13
次の例が有効であるのに他の例が無効である理由を説明できますか?

2
知らないものについては、これは合法である:hasVowel = let^M vowels = "AEIOUaeiou"^M in ...^M改行がある)
トーマス・Eding

5

私はLYHFGGからのこの例が役に立ったと感じました:

ghci> 4 * (let a = 9 in a + 1) + 2  
42  

let式なので、式を配置できるlet 場所(!)を配置できます。

つまり、上記の例では、単純に置換することはできません(おそらく、と組み合わせたより詳細な式を使用せずに)。whereletcasewhere


3

悲しいことに、ここでの答えのほとんどは初心者には技術的すぎます。

LHYFGGには関連する章があります-まだ読んでいない場合は読む必要がありますが、本質的には次のとおりです。

  • where関数の定義でのみ役立つ構文構文(ではありません)です。
  • let ... inそれ自体式なので、式を配置できる場所ならどこでも使用できます。それ自体が式であるため、ガードの拘束に使用できません。

最後に、letリスト内包表記でも使用できます。

calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
-- w: width
-- h: height

述語と同じように、リスト内包にletを含めます。リストをフィルタリングせず、名前にバインドするだけです。リスト内包内のletで定義された名前は、出力関数(の前の部分|)と、バインディングの後に続くすべての述語とセクションに表示されます。したがって、関数が25以上の人のBMIのみを返すようにすることができます。


これが実際に違いを理解するのに役立つ唯一の答えです。技術的な回答は、より経験のあるHaskell-erにとって役立つかもしれませんが、これは私のような初心者にとっては十分に簡潔です!+1
ザックG
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.