函式在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
模式匹配是匹配特定型別的表示式的過程。它不過是一種簡化程式碼的技術。可以將這種技術實現為任何型別的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
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
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)
遞迴是一種函式反復呼叫自身的情況。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
到目前為止,我們已經看到,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 "tw511.com"
在上面的範例中,我們使用了Type Char
類的toUpper
函式將輸入轉換為大寫。在這裡,方法map
將一個函式作為引數並返回所需的輸出。下面是函式的輸出:
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"TW511.COM"
有時,我們需要編寫一個在應用程式的整個生命週期中只能使用一次的函式。為了應對這種情況,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