在哈斯克尔,我们什么时候使用 In with 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

剪辑

好吧,人们似乎不明白我在说什么。让我重新措辞: 考虑到上面的情况,下面两个是相同的吗?

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中宣布的约束力范围的另一个问题:

where条款。

有时,可以方便地在几个守护方程上进行范围绑定,这需要一个 where 子句:

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

注意,这不能用 let 表达式来完成,因为 let 表达式只能作用于表达式 它包围着

我的问题是: 变量数字不应该在最后一个打印短语中可见。我是不是漏掉了什么?

88318 次浏览

In do notation, you can indeed use let with and without in. For it to be equivalent (in your case, I'll later show an example where you need to add a second do and thus more indentation), you need to indent it as you discovered (if you're using layout - if you use explicit braces and semicolons, they're exactly equivalent).

To understand why it's equivalent, you have to actually grok monads (at least to some degree) and look at the desugaring rules for do notation. In particular, code like this:

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

is translated to let x = ... in do { stmts }. In your case, stmts = print (problem_8 digits). Evaluating the whole desugared let binding results in an IO action (from print $ ...). And here, you need understanding of monads to intuitively agree that there's no difference between do notations and "regular" language elements describing a computation resulting in monadic values.

As for both why are possible: Well, let ... in ... has a broad range of applications (most of which have nothing to do with monads in particular), and a long history to boot. let without in for do notation, on the other hand, seems to be nothing but a small piece of syntactic sugar. The advantage is obvious: You can bind the results of pure (as in, not monadic) computations to a name without resorting to a pointless val <- return $ ... and without splitting up the do block in two:

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

The reason you don't need an extra do block for what follows the let is that you only got a single line. Remember, do e is e.

Regarding your edit: digit being visible in the next line is the whole point. And there's no exception for it or anything. do notation becomes one single expression, and let works just fine in a single expression. where is only needed for things which aren't expressions.

For the sake of demonstration, I'll show the desugared version of your do block. If you aren't too familiar with monads yet (something you should change soon IMHO), ignore the >>= operator and focus on the let. Also note that indentation doesn't matter any more.

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

Some beginner notes about "are following two the same".

For example, add1 is a function, that add 1 to number:

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

So, it's like add1 x = x + inc with substitution inc by 1 from let keyword.

When you try to suppress in keyword

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

you've got parse error.

From documentation:

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

Btw, there are nice explanation with many examples about what where and in keyword actually do.

Short answer: Use let without in in the body of a do-block, and in the part after the | in a list comprehension. Anywhere else, use let ... in ....


The keyword let is used in three ways in Haskell.

  1. The first form is a let-expression.

    let variable = expression in expression
    

    This can be used wherever an expression is allowed, e.g.

    > (let x = 2 in x*2) + 3
    7
    
  2. The second is a let-statement. This form is only used inside of do-notation, and does not use in.

    do statements
    let variable = expression
    statements
    
  3. The third is similar to number 2 and is used inside of list comprehensions. Again, no in.

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

    This form binds a variable which is in scope in subsequent generators and in the expression before the |.


The reason for your confusion here is that expressions (of the correct type) can be used as statements within a do-block, and let .. in .. is just an expression.

Because of the indentation rules of haskell, a line indented further than the previous one means it's a continuation of the previous line, so this

do let x = 42 in
foo

gets parsed as

do (let x = 42 in foo)

Without indentation, you get a parse error:

do (let x = 42 in)
foo

In conclusion, never use in in a list comprehension or a do-block. It is unneccesary and confusing, as those constructs already have their own form of let.

First off, why hugs? The Haskell Platform is generally the recommended way to go for newbies, which comes with GHC.

Now then, on to the letkeyword. The simplest form of this keyword is meant to always be used with in.

let {assignments} in {expression}

For example,

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

The {assignments} are only in scope in the corresponding {expression}. Regular layout rules apply, meaning that in must be indented at least as much as the let that it corresponds to, and any sub-expressions pertaining to the let expression must likewise be indented at least as much. This isn't actually 100% true, but is a good rule of thumb; Haskell layout rules are something you will just get used to over time as you read and write Haskell code. Just keep in mind that the amount of indentation is the main way to indicate which code pertains to what expression.

Haskell provides two convenience cases where you don't have to write in: do notation and list comprehensions (actually, monad comprehensions). The scope of the assignments for these convenience cases is predefined.

do foo
let {assignments}
bar
baz

For do notation, the {assignments} are in scope for any statements that follow, in this case, bar and baz, but not foo. It is as if we had written

do foo
let {assignments}
in do bar
baz

List comprehensions (or really, any monad comprehension) desugar into do notation, so they provide a similar facility.

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

The {assignments} are in scope for the expressions bar and baz, but not for foo.


where is somewhat different. If I'm not mistaken, the scope of where lines up with a particular function definition. So

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

the {assignments} in this where clause have access to x and y. guard1, guard2, blah1, and blah2 where0 have access to the {assignments} of this where clause. As is mentioned in the tutorial you linked, this can be helpful if multiple guards reuse the same expressions.