2016-11-27 23 views
4

我是一名C++/Java程序員,我正在努力學習Haskell(以及一般的函數式編程),並且我一直在努力。有一兩件事我想是這樣的:Haskell的「do」關鍵字有什麼作用?

isEven :: Int -> Bool 
isEven x = 
    if mod x 2 == 0 then True 
    else False 

isOdd :: Int -> Bool 
isOdd x = 
    not (isEven x) 

main = 
    print (isEven 2) 
    print (isOdd 2) 

但是在編譯期間這個失敗,此錯誤:

ghc --make doubler.hs -o Main 
[1 of 1] Compiling Main    (doubler.hs, doubler.o) 

doubler.hs:11:5: error: 
    • Couldn't match expected type ‘(a0 -> IO()) -> Bool -> t’ 
       with actual type ‘IO()’ 
    • The function ‘print’ is applied to three arguments, 
     but its type ‘Bool -> IO()’ has only one 
     In the expression: print (isEven 2) print (isOdd 2) 
     In an equation for ‘main’: main = print (isEven 2) print (isOdd 2) 
    • Relevant bindings include main :: t (bound at doubler.hs:10:1) 
make: *** [all] Error 1 

於是,我看到了一些代碼,在網上與「做」的關鍵字,所以我想它像這樣:

isEven :: Int -> Bool 
isEven x = 
    if mod x 2 == 0 then True 
    else False 

isOdd :: Int -> Bool 
isOdd x = 
    not (isEven x) 

main = do 
    print (isEven 2) 
    print (isOdd 2) 

它的工作原理和我以爲應該完全一樣。

這是怎麼回事?爲什麼第一個代碼片段不起作用?添加「做」實際上是做什麼的?

PS。我在互聯網上看到有關「do」關鍵字的「單子」,這是否與此有關?

+0

我覺得這個問題太寬泛了,你最好去閱讀一本haskell書中的'Monad'章節。您可以通過在本網站或Google上搜索「desugaring do notation haskell」來回答您的問題,但您可能需要更多背景信息 – jberryman

+3

['do' notation](https://en.wikibooks.org/wiki/ Haskell/do_notation)是普通一元代碼的糖。如果沒有'do',你可以寫'main = print(isEven 2)>> print(isOdd 2)'。 – Alec

回答

12

爲什麼不第一代碼片段工作?

do塊之外,換行符沒有任何意義。因此,您的main的第一個定義相當於main = print (isEven 2) print (isOdd 2),由於print只接受一個參數,因此該定義失敗。

現在你可能會想知道爲什麼我們不能只用換行符來表示一個函數應該被調用。問題在於,Haskell(通常)是惰性的並且是純粹的函數,所以函數沒有副作用,並且沒有有意義的調用一個函數的概念。

那麼print怎麼樣? print是一個接受字符串併產生IO()類型結果的函數。 IO是一種代表可能會產生副作用的類型。 main生成此類型的值,然後執行由該值描述的操作。儘管沒有一個接一個地調用一個函數的有意義的概念,但在另一個函數之後執行一個IO值的操作是有意義的概念。爲此,我們使用>>運算符,它將兩個IO值鏈接在一起。

我在互聯網上看到有關「do」關鍵字的「單子」,這是否與此有關?

是,Monad是一種類(如果你不知道這些尚未:他們是類似於OO語言界面),它(等等)提供的功能>>>>=IO是該類型類的一個實例(以OO術語:實現該接口的一種類型),它使用這些方法將多個操作彼此鏈接起來。

do語法是使用>>>>=這些函數的更方便的方法。特別是你的主要定義等同於以下不do

main = (print (isEven 2)) >> (print (isOdd 2)) 

(額外的括號是沒有必要的,但我加入他們,以避免有關優先混淆。)

所以main產生的IO執行print (isEven 2)步驟的值,然後是print (isOdd 2)的值。

4

我認爲暫時你只需要接受它。是的,do -notation是monad類型的語法糖。您的代碼可以被脫到以下幾點:

main = print (isEven 2) >> print (isOdd 2) 

(>>)意味着像這樣做後,在這種特殊情況下。然而,在嘗試解釋StackOverflow答案中的Haskell IO和monad時確實沒有什麼好處。相反,我建議您繼續學習,直到您的書或任何您用作學習資源的內容涵蓋該主題。

然而,這裏有一個快速的例子,你可以在IO - do裏面做什麼。不要太在意語法。

import System.IO 
main = do 
    putStr "What's your name? " -- Print strings 
    hFlush stdout    -- Flush output 
    name <- getLine    -- Get input and save into variable name 
    putStrLn ("Hello " ++ name) 
    putStr "What's your age? " 
    hFlush stdout 
    age <- getLine 
    putStr "In one year you will be " 
    print (read age + 1)   -- convert from string to other things with read 
           -- use print to print things that are not strings 
+1

你可以使用多於兩條語句的「do」語法嗎? – Dovahkiin

+2

是的,你可以儘可能多地使用它。 – jpath

+1

您可能希望在putStr調用之後添加'hFlush stdout',以確保在行緩衝打開時顯示提示。 –

0

你知道一個函數的結果應該只依賴於它的輸入,讓我們的模型print以反映:

print :: String -> RealWorld -> (RealWorld,()) 

main則是這樣的:

main rw0 = let (rw1, _) = print (isEven 2) rw0 in 
          print (isOdd 2) rw1 

現在,讓我們定義bind f g rw = let (rw', ret) = f rw in g rw'這是否通過RealWorld狀態交換並重寫片段以使用它:

main = bind (print (isEven 2)) 
      (print (isOdd 2)) 

現在讓我們來介紹一些語法糖,做了bind荷蘭國際集團爲我們

main = do print (isEven 2) 
      print (isOdd 2) 
3

Haskell函數是「純粹」的,並沒有測序的概念,除了「數據依賴」:使用的函數的結果值作爲另一個論點。在基本層面上,沒有要排序的語句,只有值。

有一個叫做IO的類型構造函數。它可以應用於其他類型:IO Int,IO Char,IO StringIO sometype意思是:「這個值是在現實世界中做一些東西的配方,並且一旦配方由運行時執行,返回值爲sometype」。

這就是爲什麼main的型號爲IO()。你給現實世界中的東西配方。 ()是隻有一個值的類型,它不提供任何信息。 main僅針對其在現實世界中的效果而被執行。

有一些運營商結合IO食譜。一個簡單的方法是>>需要兩個配方,並返回執行第一個配方的配方,然後返回第二個配方。注意,即使複合配方實際上類似於命令式編程的順序語句(「打印此消息,然後是其他消息」),也可以純粹的方式使用單純的函數完成組合。

爲了簡化這些「命令式食譜」的構建,編號已創建。它可以讓你編寫類似於命令式語言的順序語句的東西,但它可以解析函數應用程序。你可以用符號來寫所有的東西,你可以用常規的函數應用來寫(有時不太清楚)。