簡體   English   中英

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

[英]In Haskell, when do we use in with 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聲明的綁定范圍的另一個問題:我在這里讀到:

where條款。

有時,將綁定范圍擴展到幾個保護方程是很方便的,這需要where子句:

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

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

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

簡短的回答 :使用let沒有in在一個do塊體,並在之后的部分| 在列表理解中。 在其他任何地方,使用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)] 

    此形式綁定一個變量,該變量在后續生成器和|之前的表達式中


你在這里混淆的原因是表達式(正確類型)可以用作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形式。

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

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

let {assignments} in {expression}

例如,

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

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

哈斯克爾提供了兩個方便的情況下,您不必in :做標記和list解析(實際上,單子內涵)。 這些便利案例的分配范圍是預定義的。

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}

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

do記號,你的確可以使用let有和沒有in 為了它是等價的(在你的情況下,我稍后將展示你需要添加第二個do並因此更多縮進的示例),你需要在發現時縮進它(如果你使用布局 - 如果你使用顯式括號和分號,它們完全相同)。

要理解為什么它是等價的,你必須真正神交單子(至少在一定程度上),並查看了脫糖的規則do記號。 特別是,像這樣的代碼:

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 notations和描述導致monadic值的計算的“常規”語言元素之間沒有區別。

至於兩者為什么都有可能:嗯, let ... in ...有廣泛的應用程序(其中大多數與monad無關),以及很長的啟動歷史。 let沒有indo記號,在另一方面,似乎只是一小片的語法糖。 優點是顯而易見的:你可以將純(不是monadic)計算的結果綁定到一個名稱,而不需要求助於無意義的val <- return $ ...並且不將do塊拆分為兩個:

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

你並不需要一個額外的理由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))

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

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

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

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

當你試圖壓制in關鍵字

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

你有解析錯誤。

來自文檔

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

順便說一句,有很好的解釋有什么例子很多wherein關鍵字實際上做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM