Haskell函數

函數在Haskell中起主要作用,因為Haskell是一種函數式編程語言。與其他語言一樣,Haskell確實具有自己的函數定義和聲明。

函數聲明由函數名稱,其參數列表以及其輸出組成。函數定義是實際定義函數的地方。讓我們看看一個添加函數的示例,以詳細瞭解此概念。

Live Demo
add :: Integer -> Integer -> Integer   --function declaration
add x y =  x + y                       --function definition

main = do
   putStrLn "The addition of the two numbers is:"
   print(add 2 5)    --calling a function

在這裏,在第一行中聲明了函數,在第二行中,我們編寫了實際的函數,該函數將帶有兩個參數並產生一個整數類型的輸出。
與大多數其他語言一樣,Haskell從main方法開始編譯代碼,代碼將生成以下輸出:

The addition of the two numbers is:
7

1. 模式匹配

模式匹配是匹配特定類型的運算式的過程。它不過是一種簡化代碼的技術。可以將這種技術實現為任何類型的Type類。If-Else可以用作模式匹配的替代選項。

模式匹配可以看作是動態多態的一種變體,在運行時,可以根據參數列表執行不同的方法。下麵的代碼塊中使用模式匹配技術來計算數字的階乘。

fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )

main = do
   putStrLn "The factorial of 5 is:"
   print (fact 5)

我們都知道如何計算數字的階乘。編譯器將開始搜索帶有參數的函數fact。如果參數不等於0,則數字將繼續調用比實際參數小1的相同函數。

當參數的模式與0完全匹配時,它將調用模式fact 0 = 1。上面代碼將產生以下輸出:

The factorial of 5 is:
120

2. Guards

Guards是一個與模式匹配非常相似的概念。在模式匹配中,通常匹配一個或多個運算式,但是使用Guards來測試運算式的某些屬性。

儘管建議對Guards使用模式匹配,但是從開發人員的角度來看,Guards更加可讀和簡單。對於初次使用的用戶,Guards看上去與If-Else語句非常相似,但是它們在功能上有所不同。

在以下代碼中,我們使用Guards的概念來實現階乘程式。

fact :: Integer -> Integer
fact n | n == 0 = 1
       | n /= 0 = n * fact (n-1)
main = do
   putStrLn "The factorial of 5 is:"
   print (fact 5)

在這裏,我們聲明了兩個guards,以|分隔並從main調用fact函數。在內部,編譯器將以與模式匹配的方式相同的方式工作,以產生以下輸出:

The factorial of 5 is:
120

3. Where字句

where關鍵字或內置函數,可在運行時用於生成所需的輸出。當函數計算變得複雜時,這將非常有用。

如果輸入是具有多個參數的複雜運算式。在這種情況下,可以使用where子句將整個運算式分解成小部分。
在下面的示例中,我們採用一個複雜的數學運算式來展示如何使用Haskell計算多項式方程[x ^ 2-8x + 6]的根。

roots :: (Float, Float, Float) -> (Float, Float)
roots (a,b,c) = (x1, x2) where
   x1 = e + sqrt d / (2 * a)
   x2 = e - sqrt d / (2 * a)
   d = b * b - 4 * a * c
   e = - b / (2 * a)
main = do
   putStrLn "The roots of our Polynomial equation are:"
   print (roots(1,-8,6))

注意,計算給定多項式函數的根的運算式很複雜。因此,使用where子句打斷長的運算式。上面的代碼將生成以下輸出:

The roots of our Polynomial equation are:
(7.1622777,0.8377223)

4. 遞歸函數

遞歸是一種函數反復調用自身的情況。Haskell不提供任何多次迴圈任何運算式的功能。Haskell希望您將整個函數分解為不同函數的集合,並使用遞歸技術來實現函數。

再次考慮前面模式匹配示例,在該示例中,我們已經計算了數字的階乘。查找數字的階乘是使用遞歸的經典案例。在這裏,您可能會想問:“模式匹配與遞歸有什麼不同?”這兩者之間的區別在於它們的使用方式。模式匹配用於設置終端約束,而遞歸是一個函數調用。

在以下示例中,同時使用了模式匹配和遞歸來計算階乘5

Live Demo
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )

main = do
   putStrLn "The factorial of 5 is:"
   print (fact 5)

執行上面代碼,得到以下結過:

The factorial of 5 is:
120

4. 高階函數

到目前為止,我們已經看到,Haskell函數將一種類型作為輸入,並產生另一種類型作為輸出,這在其他命令式語言中非常相似。高階函數是Haskell的獨特功能,可以將函數用作輸入或輸出參數。

儘管這是一個虛擬的概念,但是在實際程式中,Haskell中定義的每個函數都使用高階機制來提供輸出。如果您有機會研究Haskell的庫函數,那麼就會發現大多數庫函數都是以高階編寫的。

讓我們以一個示例為例,在該示例中將導入一個內置的高階函數映射,並根據選擇使用該映射來實現另一個高階函數。

import Data.Char
import Prelude hiding (map)

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map func (x : abc) = func x : map func abc
main = print $ map toUpper "xuhuhu.com"

在上面的示例中,我們使用了Type Char類的toUpper函數將輸入轉換為大寫。在這裏,方法map將一個函數作為參數並返回所需的輸出。下麵是函數的輸出:

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"xuhuhu.com"

5. Lambda運算式

有時,我們需要編寫一個在應用程式的整個生命週期中只能使用一次的函數。為了應對這種情況,Haskell開發人員使用了另一個稱為lambda運算式或lambda函數的匿名塊。

沒有定義的函數稱為lambda函數。Lambda函數由\字元表示。看看下麵的一個示例,在不創建任何函數的情況下將輸入值增加1

main = do
   putStrLn "The successor of 4 is:"
   print ((\x -> x + 1) 4)

在這裏,創建了一個沒有名稱的函數(匿名函數)。它以整數4作為參數,並輸出一個值。這是一個只操作一次的函數,所以沒有必要聲明它,直接使用lambda運算式來實現。

上面lambda運算式將產生以下輸出結果:

sh-4.3$ main
The successor of 4 is:
5