78

次のコードでは、最後のフレーズを前に置くことができ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.1。

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

2.2。

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

で宣言されたバインディングの範囲に関する別の質問:私はここletでそれを読みました:

where条項。

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

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

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

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

4

4 に答える 4

148

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


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

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

    let variable = expression in expression
    

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

    > (let x = 2 in x*2) + 3
    7
    
  2. 2番目はletステートメントです。この形式は、do-notation内でのみ使用され、を使用しません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-block内のステートメントとして使用でき、let .. in ..単なる式であるためです。

haskellのインデント規則により、前の行よりもさらにインデントされた行は、前の行の続きであることを意味します。したがって、これは

do let x = 42 in
     foo

として解析されます

do (let x = 42 in foo)

インデントがないと、解析エラーが発生します。

do (let x = 42 in)
   foo

結論としてin、リスト内包表記やdo-blockで使用しないでください。これらの構成にはすでに独自の形式のが含まれているため、これは不必要で混乱を招きletます。

于 2011-11-25T22:41:15.917 に答える
23

まず、なぜハグするのですか?Haskellプラットフォームは、一般的に、GHCに付属している初心者に推奨される方法です。

それでは、letキーワードに移りましょう。このキーワードの最も単純な形式は、常に。とともに使用されることを意図していますin

let {assignments} in {expression}

例えば、

let two = 2; three = 3 in two * three

は、対応するの範囲内に{assignments}のみあり{expression}ます。通常のレイアウトルールが適用されます。つまり、in少なくともlet対応するものと同じだけインデントする必要があり、式に関連するサブ式letも同様に少なくとも同じくらいインデントする必要があります。これは実際には100%真実ではありませんが、経験則としては適切です。Haskellのレイアウトルールは、Haskellコードを読み書きするときに、時間の経過とともに慣れていくものです。インデントの量が、どのコードがどの式に関係するかを示す主な方法であることを覚えておいてください。

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

do foo
   let {assignments}
   bar
   baz

do表記の場合、は、この場合は、および{assignments}に続くすべてのステートメントの範囲内にありますが、 。には含まれません。まるで書いたかのようですbarbazfoo

do foo
   let {assignments}
   in do bar
         baz

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

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

は式と{assignments}のスコープ内にありますが、のスコープ内にはありません。barbazfoo


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

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

{assignments}このwhere節のはとxにアクセスできますyguard1、、、およびすべてguard2がこの句のにアクセスできます。リンクしたチュートリアルで説明したように、これは、複数のガードが同じ式を再利用する場合に役立ちます。blah1blah2 {assignments}where

于 2011-11-25T23:00:03.060 に答える
7

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

なぜそれが同等であるかを理解するには、実際にモナドを(少なくともある程度は)グロッキングし、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なしinでは、構文糖衣の小さな断片にすぎないようです。利点は明らかです。無意味に頼ることなく、またブロックを2つに分割することdoなく、純粋な(モナディックではない)計算の結果を名前にバインドできます。val <- return $ ...do

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

do次のブロックに追加のブロックが必要ない理由letは、1行しか取得できないためです。覚えておいてdo eくださいe

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

デモンストレーションのために、あなたのdoブロックの脱糖バージョンを紹介します。モナドにまだ慣れていない場合(私見ですぐに変更する必要があります)、>>=演算子を無視して、に焦点を合わせletます。また、インデントはもはや重要ではないことに注意してください。

main = readFile "p8.log" >>= (\t ->
  let digits = map digitToInt $ concat $ lines t
  in print (problem_8 digits))
于 2011-11-25T22:36:10.987 に答える
1

「同じ2つをフォローしている」という初心者向けのメモもあります。

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

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

つまり、キーワードadd1 x = x + incから1でincを置換するようなものです。let

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

于 2011-11-25T22:37:05.510 に答える