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

McB*_*den 56 whitespace haskell scope let where-clause

在下面的代码中,我可以把最后一个词放在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
Run Code Online (Sandbox Code Playgroud)

编辑

好的,所以人们似乎不明白我在说什么.让我重新说一下:鉴于上述背景,以下两个是否相同?

1.

let digits = map digitToInt $concat $ lines t
print $ problem_8 digits
Run Code Online (Sandbox Code Playgroud)

2.

let digits = map digitToInt $concat $ lines t
in print $ problem_8 digits
Run Code Online (Sandbox Code Playgroud)

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

where 条款.

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

f x y  |  y>z           =  ...
       |  y==z          =  ...
       |  y<z           =  ...
     where z = x*x
Run Code Online (Sandbox Code Playgroud)

请注意,这不能通过let表达式来完成,该表达式仅覆盖它所包含的表达式.

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

ham*_*mar 115

简短的回答:使用let没有in在做块体,并在之后的部分|以列表的理解.在其他地方,使用let ... in ....


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

  1. 第一种形式是let-expression.

    let variable = expression in expression
    
    Run Code Online (Sandbox Code Playgroud)

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

    > (let x = 2 in x*2) + 3
    7
    
    Run Code Online (Sandbox Code Playgroud)
  2. 第二个是let-statement.此表单仅在do-notation中使用,不使用in.

    do statements
       let variable = expression
       statements
    
    Run Code Online (Sandbox Code Playgroud)
  3. 第三个类似于数字2,在列表推导中使用.再一次,没有in.

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

    此形式绑定一个变量,该变量在后续生成器和之前的表达式中的范围内|.


你在这里混淆的原因是表达式(正确的类型)可以用作do-block中的语句,并且let .. in ..只是一个表达式.

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

do let x = 42 in
     foo
Run Code Online (Sandbox Code Playgroud)

被解析为

do (let x = 42 in foo)
Run Code Online (Sandbox Code Playgroud)

没有缩进,您会得到一个解析错误:

do (let x = 42 in)
   foo
Run Code Online (Sandbox Code Playgroud)

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

  • 非常次要:let 绑定中的 LHS 是模式匹配,而不仅仅是变量绑定。 (2认同)

Dan*_*ton 18

首先,为什么拥抱?该哈斯克尔平台一般是去新手,附带GHC的推荐方式.

现在,转到let关键字.此关键字的最简单形式是始终与之一起使用in.

let {assignments} in {expression}
Run Code Online (Sandbox Code Playgroud)

例如,

let two = 2; three = 3 in two * three
Run Code Online (Sandbox Code Playgroud)

{assignments}在对应的范围{expression}.应用常规布局规则,这意味着in必须缩进至少let与其对应的缩进量,并且与表达式相关的任何子let表达式同样必须至少缩进.这实际上并非100%正确,但这是一个很好的经验法则; Haskell布局规则是您在读写Haskell代码时会习惯的.请记住,缩进量是指示哪些代码与哪个表达式相关的主要方式.

Haskell提供了两个方便的例子,你不必in:do notation和list comprehensions(实际上,monad comprehensions).这些便利案例的分配范围是预定义的.

do foo
   let {assignments}
   bar
   baz
Run Code Online (Sandbox Code Playgroud)

对于do符号中,{assignments}在范围为遵循,在这种情况下,任何陈述barbaz,但不会foo.就好像我们写了一样

do foo
   let {assignments}
   in do bar
         baz
Run Code Online (Sandbox Code Playgroud)

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

[ baz | foo, let {assignments}, bar ]
Run Code Online (Sandbox Code Playgroud)

{assignments}在范围内的表达barbaz,但不适合foo.


where有点不同.如果我没有弄错的话,where用特定的函数定义排列的范围.所以

someFunc x y | guard1 = blah1
             | guard2 = blah2
  where {assignments}
Run Code Online (Sandbox Code Playgroud)

{assignments}where条访问xy.guard1,guard2,blah1,和blah2 所有有机会获得{assignments}本的where条款.正如您链接的教程中所提到的,如果多个警卫重用相同的表达式,这可能会有所帮助.


小智 7

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

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

do let x = ...
   stmts -- the rest of the do block
Run Code Online (Sandbox Code Playgroud)

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

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

do stuff
   let val = ...
    in do more
          stuff $ using val
Run Code Online (Sandbox Code Playgroud)

你之后不需要额外do块的原因let是你只有一行.记住,do ee.

关于你的编辑:digit在下一行中可见是重点.它也不例外.do符号变成一个单独的表达式,并且let在单个表达式中工作得很好.where只有非表达式的东西才需要.

为了演示,我将展示你的do块的desugared版本.如果你还不太熟悉monad(你应该尽快更改恕我直言),忽略>>=操作员并专注于let.另请注意,缩进不再重要.

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