在Haskell中,我们何时使用let?

时间:2011-11-25 22:06:10

标签: haskell scope whitespace where-clause 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中声明的绑定范围的另一个问题:我读了here

  

where条款。

有时,将绑定范围扩展到几个保护方程是很方便的,这需要一个where子句:

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

请注意,使用let表达式无法做到这一点,该表达式仅覆盖所包含的表达式

我的问题:所以,可变数字不应该对最后一个印刷短语可见。我在这里想念一下吗?

4 个答案:

答案 0 :(得分:109)

简短回答:在do-block的正文中使用let而不使用in,在列表理解中使用|之后的部分。在其他任何地方,请使用let ... in ...


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

  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)]
    

    此表单绑定一个变量,该变量位于后续生成器和|之前的表达式中。


  4. 这里混淆的原因是表达式(正确类型)可以用作do-block中的语句,而let .. in ..只是表达式。

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

    do let x = 42 in
         foo
    

    被解析为

    do (let x = 42 in foo)
    

    如果没有缩进,则会出现解析错误:

    do (let x = 42 in)
       foo
    

    总之,永远不要在列表理解或阻止中使用in。这是不必要的和令人困惑的,因为这些结构已经有let的形式。

答案 1 :(得分:17)

首先,为什么拥抱? Haskell Platform通常是推荐使用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:做符号和列表推导(实际上,monad comprehensions)。这些便利案例的分配范围是预定义的。

do foo
   let {assignments}
   bar
   baz

对于do表示法,{assignments}适用于以下任何语句,在本例中为barbaz,但不包括foo。就好像我们写了

do foo
   let {assignments}
   in do bar
         baz

列出理解(或者实际上,任何monad理解)desugar into do notation,所以他们提供类似的设施。

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

{assignments}适用于barbaz这两个词,但不适用于foo


where有些不同。如果我没有弄错的话,where的范围与特定的函数定义对齐。所以

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

{assignments}子句中的where可以访问xyguard1guard2blah1blah2 所有都可以访问此{assignments}子句的where。正如您链接的教程中所提到的,如果多个警卫重用相同的表达式,这可能会有所帮助。

答案 2 :(得分:7)

do表示法中,您确实可以使用let使用in。为了它是等价的(在你的情况下,我稍后将展示一个你需要添加第二个do并因此更多缩进的例子),你需要在发现时缩进它(如果你使用的是布局) - 如果你使用显式括号和分号,它们就完全等价了。

要理解为什么它是等价的,你必须实际修改monad(至少在某种程度上)并查看do符号的desugaring规则。特别是,像这样的代码:

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

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

至于两者为什么都可能:嗯,let ... in ...有广泛的应用程序(其中大多数与monad无关),以及很长的启动历史。另一方面,let没有in do符号val <- return $ ...似乎只是一小段语法糖。优点是显而易见的:您可以将纯(不是monadic)计算的结果绑定到名称而不诉诸无意义do并且不将do stuff let val = ... in do more stuff $ using val 块拆分为两个:

do

您在let后面不需要额外do e块的原因是您只有一行。请注意,edigit

关于您的修改:do在下一行中可见是重点。它也不例外。 let表示法成为单个表达式,where在单个表达式中工作正常。只有非表达式的东西才需要do

为了演示,我将展示您>>=块的desugared版本。如果你还不熟悉monad(你应该尽快更改恕我直言),忽略let运算符并专注于main = readFile "p8.log" >>= (\t -> let digits = map digitToInt $ concat $ lines t in print (problem_8 digits)) 。另请注意,缩进不再重要。

{{1}}

答案 3 :(得分:1)

一些初学者注意到“跟随两个相同”。

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

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

所以,就像add1 x = x + inc一样,let关键字的替换值为1。

当您尝试取消in关键字

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

你有解析错误。

来自documentation

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

顺便说一下,nice explanation有许多关于wherein关键字实际执行的示例。