在Haskell中,我们什么时候用let?

在下面的代码中,我可以放在前面的最后一个短语。 它会改变什么吗?

另一个问题:如果我决定放在最后一句话前面,我需要缩进吗?

我尝试没有缩进和拥抱抱怨

do {…}中的最后一个生成器必须是一个expression式

 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 

另外一个问题是关于在声明中声明的绑定的范围:我在这里读到:

条款的地方。

有时,将绑定范围限制在几个有保护的方程上是很方便的,这需要一个where子句:

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

请注意,这不能用一个letexpression式来完成,该expression式只覆盖它所包含的expression式。

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

简短的回答 :在do块的内部,在|之后的部分使用let 在列表中理解。 其他地方,请let ... in ...


关键字let在Haskell中有三种使用方式。

  1. 第一种forms是一个expression式

     let variable = expression in expression 

    这可以用在任何允许expression的地方,例如

     > (let x = 2 in x*2) + 3 7 
  2. 第二个是发言 。 这个表格只用在do-notation里面,并没有用in

     do statements let variable = expression statements 
  3. 第三类似于第二类,用于列表parsing。 再次,没有in

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

    这个forms绑定了一个variables,这个variables在后面的生成器中的作用域和在|之前的expression式中 。


这里你的困惑的原因是expression式(正确的types)可以用作do-block中的语句, let .. in ..只是一个expression式。

由于haskell的缩进规则,比上一行缩进的行意味着它是前一行的延续,所以这

 do let x = 42 in foo 

被parsing为

 do (let x = 42 in foo) 

没有缩进,你会得到一个parsing错误:

 do (let x = 42 in) foo 

总之,不要用in列表理解或do-block中。 这是不必要的和混乱的,因为这些构造已经有了自己的forms。

首先,为什么拥抱? 一般来说, Haskell平台是推荐给GHC的新手的方法。

现在,然后,到关键字。 这个关键字的最简单的forms意味着总是in一起使用。

 let {assignments} in {expression} 

例如,

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

{assignments} 在相应的{expression}范围内。 应用规则的布局规则,这意味着in必须缩进至less与let对应的缩放,并且与letexpression式有关的任何子expression式也必须至less被缩进。 这实际上不是100%真实的,而是一个很好的经验法则。 Haskell布局规则是随着时间的推移,随着时间的推移,你读和写Haskell代码。 请记住,缩进量是指示哪些代码属于哪个expression式的主要方法。

Haskell提供了两个方便的例子,你不需要写下:符号和列表parsing(实际上是monadparsing)。 这些便利案件的分配范围是预定义的。

 do foo let {assignments} bar baz 

为了表示, {assignments}在后面的任何语句的范围内,在这种情况下, barbaz ,而不是foo 。 就好像我们写了一样

 do foo let {assignments} in do bar baz 

列表理解(或者真的,任何monad理解)都可以解释为符号,所以它们提供了类似的function。

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

{assignments}在expression式barbaz范围内,但不适用于foo


where有些不同。 如果我没有弄错,那么在where定义一个特定函数的范围。 所以

 someFunc xy | guard1 = blah1 | guard2 = blah2 where {assignments} 

这个where子句中的{assignments}可以访问xyguard1guard2blah1blah2 可以访问这个where子句的{assignments} 。 正如您在本教程中提到的那样,如果多个警卫重复使用相同的expression式,这会很有帮助。

在记号中,你确实可以使用let with和in 。 对于它是相当的(在你的情况,我会稍后展示一个例子,你需要添加第二个do ,因此更多的缩进),你需要缩进,因为你发现(如果你使用布局 – 如果你使用明确的括号和分号,它们完全相同)。

要理解为什么它是相同的,你必须实际上讨好单子(至less在某种程度上),并看看符号的解释规则。 特别是这样的代码:

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

被翻译为let x = ... in do { stmts } 。 在你的情况下, stmts = print (problem_8 digits) 。 评估整个desugared let绑定导致IO操作(从print $ ... )。 在这里,你需要理解单子,直观地认为,符号和描述计算导致单子值的“常规”语言元素之间没有区别。

至于为什么是可能的:那么, let ... in ...有广泛的应用(其中大部分与monad无关),并有悠久的历史启动。 另一方面, let do指责,似乎只不过是一小块语法糖。 好处是显而易见的:你可以将纯粹的计算结果绑定到一个名字上,而不需要使用无意义的val <- return $ ... ,而不需要将do块分成两部分:

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

你之所以不需要额外的阻止,是因为你只有一条线。 请记住, do ee

关于你的编辑: digit在下一行是可见的是整个点。 而且没有任何例外。 符号变成一个单一的expression式, let单纯的expression式工作得很好。 只有那些不是expression式的东西才需要。

为了示范,我会显示你的do块的desugared版本。 如果你还不太熟悉单子(恕我直言,你应该马上改变),忽略>>=操作符并把重点放在let 。 另请注意,缩进无关紧要。

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

一些关于“跟随两个一样”的初学者笔记。

例如, add1是一个函数,即将数字1加1:

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

所以,它就像add1 x = x + inc其中let关键字replace为1。

当你试图in关键字压制

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

你有parsing错误。

从文档 :

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

顺便说一下,有很多关于什么where和关键字实际上做的例子很好的解释 。