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

编辑

好的,所以人们似乎不明白我在说什么。让我换个说法:鉴于上述上下文,以下两个相同吗?

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 表达式来完成,它只作用于它所包含的表达式。

我的问题:所以,最后一个打印短语不应该看到可变数字。我在这里想念什么吗?

4

4 回答 4

148

简短回答:在 do-block 的主体中以及在列表理解之后的部分中使用letwithout 。在其他任何地方,使用.in|let ... in ...


letHaskell 中以三种方式使用关键字。

  1. 第一种形式是let-expression

    let variable = expression in expression
    

    这可以在任何允许使用表达式的地方使用,例如

    > (let x = 2 in x*2) + 3
    7
    
  2. 第二个是let-statement。这种形式只在 do-notation 内部使用,不使用in.

    do statements
       let variable = expression
       statements
    
  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 提供了两种无需编写的便利案例in:do 表示法和列表推导(实际上是 monad 推导)。这些便利案例的分配范围是预定义的。

do foo
   let {assignments}
   bar
   baz

对于do符号,{assignments}是在后面的任何语句的范围内,在这种情况下,barand baz,但不是foo。就好像我们写过

do foo
   let {assignments}
   in do bar
         baz

列表推导(或实际上,任何单子推导)脱糖为 do 表示法,因此它们提供了类似的工具。

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

{assignments}位于表达式barand的范围内baz,但不在foo.


where有点不同。如果我没记错的话,范围where与特定的函数定义一致。所以

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

{assignments}该子句中的 thewhere可以访问xand yguard1, guard2, blah1, 和blah2 所有都可以访问{assignments}这个where子句。正如您链接的教程中所述,如果多个警卫重用相同的表达式,这可能会有所帮助。

于 2011-11-25T23:00:03.060 回答
7

do符号中,您确实可以使用letwith 和 without in。为了使其等效(在您的情况下,我稍后将展示一个示例,您需要添加第二个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在这里,您需要了解 monad 才能直观地同意符号和描述导致 monadic 值的计算的“常规”语言元素之间没有区别。

至于为什么是可能的:嗯,let ... in ...具有广泛的应用程序(其中大多数与 monad 无关),并且启动历史悠久。另一方面,let没有infor符号似乎只是一小块语法糖。do优点是显而易见的:您可以将纯(如,而不是单子)计算的结果绑定到一个名称,而无需诉诸毫无意义val <- return $ ...且无需将do块分成两部分:

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

后面的内容不需要额外的do块的原因let是您只有一行。记住,do ee

关于您的编辑:digit在下一行中可见是重点。它也不例外。donotation 成为一个单一的表达式,并且let在一个表达式中工作得很好。where只有不是表达式的东西才需要。

为了演示,我将展示您的do块的脱糖版本。如果您还不太熟悉 monad(恕我直言,您应该尽快更改),请忽略>>=运算符并专注于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

一些关于“遵循两个相同”的初学者注释。

例如,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 回答