在Haskell中,我們何時使用let?



scope whitespace (3)

簡答 :在do-block的主體中使用let in ,並在|之後的部分中使用let 在列表理解中。 在其他任何地方,使用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,在列表推導中使用。 再一次,沒有。

    > [(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列表理解或阻止中使用。 這是不必要的和令人困惑的,因為這些構造已經有自己的let形式。

在下面的代碼中,我可以放在前面的最後一個短語。 它會改變什麼嗎?

另一個問題:如果我決定在最後一個短語前加入,我是否需要縮進它?

我試著沒有縮進和擁抱抱怨

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子句:

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

請注意,這不能通過let表達式來完成,該表達式僅覆蓋它所包含的表達式。

我的問題:所以,可變數字不應該對最後一個印刷短語可見。 我在這裡想念一下嗎?


Answer #1

一些初學者註意到“跟隨兩個相同”。

例如, add1是一個函數,它為數字加1:

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

所以,就像add1 x = x + inclet關鍵字的替換inc為1。

當您嘗試抑制關鍵字時

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. 

順便說一下,關於關鍵字的實際where和實際內容的實例,有很多很好的解釋


Answer #2

首先,為什麼擁抱? Haskell平台通常是推薦GHC新手的方式。

現在,轉到let關鍵字。 此關鍵字的最簡單形式是始終in一起使用。

let {assignments} in {expression}

例如,

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

{assignments} 在相應的{expression}範圍內。 應用常規佈局規則,這意味著必須縮進至少與它對應的let一樣多,並且任何與let表達式相關的子表達式同樣必須至少縮進。 這實際上並非100%正確,但這是一個很好的經驗法則; Haskell佈局規則是您在讀取和編寫Haskell代碼時會習慣的。 請記住,縮進量是指示哪些代碼與哪個表達式相關的主要方式。

Haskell提供了兩個方便的例子,你不必寫入:do notation和list comprehensions(實際上,monad comprehensions)。 這些便利案例的分配範圍是預定義的。

do foo
   let {assignments}
   bar
   baz

對於符號, {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}

where子句中的{assignments}可以訪問xyguard1guard2blah1blah2 可以訪問此where子句的{assignments} 。 正如您鏈接的教程中所提到的,如果多個警衛重用相同的表達式,這可能會有所幫助。





let