在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中有三种使用方式。
-
第一种forms是一个expression式 。
let variable = expression in expression
这可以用在任何允许expression的地方,例如
> (let x = 2 in x*2) + 3 7
-
第二个是发言 。 这个表格只用在do-notation里面,并没有用
in
。do statements let variable = expression statements
-
第三类似于第二类,用于列表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
对应的缩放,并且与let
expression式有关的任何子expression式也必须至less被缩进。 这实际上不是100%真实的,而是一个很好的经验法则。 Haskell布局规则是随着时间的推移,随着时间的推移,你读和写Haskell代码。 请记住,缩进量是指示哪些代码属于哪个expression式的主要方法。
Haskell提供了两个方便的例子,你不需要写下:符号和列表parsing(实际上是monadparsing)。 这些便利案件的分配范围是预定义的。
do foo let {assignments} bar baz
为了表示, {assignments}
在后面的任何语句的范围内,在这种情况下, bar
和baz
,而不是foo
。 就好像我们写了一样
do foo let {assignments} in do bar baz
列表理解(或者真的,任何monad理解)都可以解释为符号,所以它们提供了类似的function。
[ baz | foo, let {assignments}, bar ]
{assignments}
在expression式bar
和baz
范围内,但不适用于foo
。
where
有些不同。 如果我没有弄错,那么在where
定义一个特定函数的范围。 所以
someFunc xy | guard1 = blah1 | guard2 = blah2 where {assignments}
这个where
子句中的{assignments}
可以访问x
和y
。 guard1
, guard2
, blah1
和blah2
都可以访问这个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 e
是e
。
关于你的编辑: 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
和关键字实际上做的例子很好的解释 。