web-dev-qa-db-ja.com

Haskellでは、letでいつ使用しますか?

次のコードの最後のフレーズでは、inを前に置くことができます。それは何かを変えますか?

別の質問:inを最後のフレーズの前に置くことにした場合、インデントする必要がありますか?

インデントせずに試してみて、抱擁文句を言う

Do {...}の最後のジェネレータは式でなければなりません

import Data.Char
groupsOf _ [] = []
groupsOf n xs = 
    take n xs : groupsOf n ( tail xs )

problem_8 x = maximum . map product . groupsOf 5 $ x
main = do t <- readFile "p8.log" 
          let digits = map digitToInt $concat $ lines t
          print $ problem_8 digits

編集

わかりましたので、人々は私が言っていることを理解していないようです。言い換えると、上記のコンテキストを前提として、次の2つは同じですか?

1。

let digits = map digitToInt $concat $ lines t
print $ problem_8 digits

2。

let digits = map digitToInt $concat $ lines t
in print $ problem_8 digits

letで宣言されたバインディングのスコープに関する別の質問: here that:

where句。

Where節を必要とするいくつかの保護された方程式にバインドをスコープすると便利な場合があります。

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

これは、それが囲む式のみをスコープとするlet式では実行できないことに注意してください

私の質問:そのため、変数の数字は最後の印刷フレーズに表示されるべきではありません。ここで何かが恋しいですか?

55
McBear Holden

簡単な答え:doブロックの本体とリスト内包表記の|の後の部分でletなしでinを使用します。それ以外の場合は、let ... in ...を使用します。


Haskellでは、キーワードletは3つの方法で使用されます。

  1. 最初の形式はlet-expressionです。

    let variable = expression in expression
    

    これは、式が許可されている場所であればどこでも使用できます。

    > (let x = 2 in x*2) + 3
    7
    
  2. 2番目はlet-statementです。この形式はdo表記の内部でのみ使用され、inは使用しません。

    do statements
       let variable = expression
       statements
    
  3. 3番目は2番に似ており、リスト内包表記の中で使用されます。繰り返しますが、inはありません。

    > [(x, y) | x <- [1..3], let y = 2*x]
    [(1,2),(2,4),(3,6)]
    

    この形式は、後続のジェネレーターと|の前の式のスコープ内にある変数をバインドします。


ここで混乱する理由は、式(正しい型)をdoブロック内のステートメントとして使用でき、let .. in ..は単なる式であるためです。

Haskellのインデント規則により、前の行よりもインデントされた行は、前の行の継続であることを意味するため、

do let x = 42 in
     foo

として解析されます

do (let x = 42 in foo)

インデントなしでは、解析エラーが発生します:

do (let x = 42 in)
   foo

結論として、リスト内包表記またはdoブロックでinを使用しないでください。これらの構成体はすでに独自の形式のletを持っているため、不必要でわかりにくいです。

112
hammar

まず、なぜ抱擁しますか? Haskell Platform は、GHCに付属している初心者向けの一般的な推奨方法です。

次に、letkeywordに進みます。このキーワードの最も単純な形式は、inと共にalwaysを使用することを意味します。

_let {assignments} in {expression}
_

例えば、

_let two = 2; three = 3 in two * three
_

_{assignments}_は、対応する_{expression}_.のスコープ内のonlyです。つまり、inは少なくとも対応するletとインデントし、let式に関連するサブ式も同様に少なくともインデントする必要があります。これは実際には100%真実ではありませんが、大まかな目安です。 Haskellのレイアウトルールは、Haskellコードを読み書きするときに慣れるでしょう。インデントの量は、どのコードがどの式に関係するかを示す主な方法であることに留意してください。

Haskellには、inを記述する必要がないという2つの便利なケースがあります。表記法とリスト内包表記(実際には、モナド内包表記)です。これらの便利なケースの割り当ての範囲は事前に定義されています。

_do foo
   let {assignments}
   bar
   baz
_

do表記の場合、_{assignments}_は、後続のステートメントのスコープ内にあります。この場合、barおよびbazであり、fooではありません。書いたようです

_do foo
   let {assignments}
   in do bar
         baz
_

リスト内包表記(または実際には、任意のモナド内包表記)はdo表記に脱糖するため、同様の機能を提供します。

_[ baz | foo, let {assignments}, bar ]
_

_{assignments}_は、式barおよびbazのスコープ内にありますが、fooのスコープにはありません。


whereは多少異なります。間違っていなければ、whereのスコープは特定の関数定義と一致します。そう

_someFunc x y | guard1 = blah1
             | guard2 = blah2
  where {assignments}
_

このwhere句の_{assignments}_は、xおよびyにアクセスできます。 _guard1_、_guard2_、_blah1_、および_blah2_allは、このwhere句の_{assignments}_にアクセスできます。リンクしたチュートリアルで述べたように、これは複数のガードが同じ式を再利用する場合に役立ちます。

18
Dan Burton

do表記では、letの有無にかかわらずinを実際に使用できます。それを同等にするために(あなたの場合、2番目のdoを追加する必要があるため、インデントを増やす必要がある例を後で示します)、発見したとおりにインデントする必要があります(使用している場合)レイアウト-明示的な中括弧とセミコロンを使用する場合、それらはまったく同じです)。

whyと同等であると理解するには、実際にモナドを(少なくともある程度)理解し、do表記の脱糖規則を調べる必要があります。特に、次のようなコード:

_do let x = ...
   stmts -- the rest of the do block
_

_let x = ... in do { stmts }_に変換されます。あなたの場合、stmts = print (problem_8 digits)。脱糖されたletバインディング全体を評価すると、IOアクション(_print $ ..._から))になります。ここでは、モナドを理解する必要があります。 do表記法と、モナド値をもたらす計算を記述する「通常の」言語要素。

両方の理由が考えられます。まあ、_let ... in ..._には幅広いアプリケーションがあり(そのほとんどは特にモナドとは関係ありません)、ブートする長い歴史があります。一方、let表記法のindoなしで、わずかな構文糖に過ぎないようです。利点は明らかです。無意味な_val <- return $ ..._に頼らず、doブロックを2つに分割することなく、純粋な(モナドではなく)計算の結果を名前にバインドできます。

_do stuff
   let val = ...
    in do more
          stuff $ using val
_

doに続くものに余分なletブロックが不要な理由は、1行しか取得できないからです。 _do e_はeであることを忘れないでください。

編集に関して:digitは次の行に表示されていることがポイントです。そして、それ以外にも何も例外はありません。 do表記は単一の式になり、letは単一の式で問題なく機能します。 whereは、式ではないものにのみ必要です。

デモのために、doブロックの脱糖バージョンを示します。モナドにまだあまり慣れていない場合(すぐに変更する必要があるもの)、_>>=_演算子を無視して、letに注目してください。また、インデントは問題ではありません。

_main = readFile "p8.log" >>= (\t ->
  let digits = map digitToInt $ concat $ lines t
  in print (problem_8 digits))
_
7
user395760

「次の2つは同じです」に関するいくつかの初心者メモ。

例えば、 add1は、数値に1を加算する関数です。

add1 :: Int -> Int
add1 x =
    let inc = 1
    in x + inc

それで、add1 x = x + inc incをletキーワードから1で置換。

inキーワードを抑制しようとすると

add1 :: Int -> Int
add1 x =
    let inc = 1
    x + inc

解析エラーが発生しました。

ドキュメント から:

Within do-blocks or list comprehensions 
let { d1 ; ... ; dn } 
without `in` serves to introduce local bindings. 

ところで、 いい説明 があり、whereinキーワードが実際に何をするかについての多くの例があります。