
- Haskell 教程
- Haskell - 首頁
- Haskell - 概述
- Haskell - 環境搭建
- Haskell - 基本資料模型
- Haskell - 基本運算子
- Haskell - 決策機制
- Haskell - 型別和型別類
- Haskell - 函式
- Haskell - 函式詳解
- Haskell - 函式組合
- Haskell - 模組
- Haskell - 輸入與輸出
- Haskell - Functor (函子)
- Haskell - Monad (單子)
- Haskell - Zipper (拉鍊)
- Haskell 有用資源
- Haskell - 快速指南
- Haskell - 有用資源
- Haskell - 討論
Haskell - 函式
函式在 Haskell 中扮演著重要的角色,因為它是一種函數語言程式設計語言。與其他語言一樣,Haskell 也有其自身的函式定義和宣告。
函式宣告由函式名、引數列表及其輸出組成。
函式定義是您實際定義函式的地方。
讓我們來看一個簡單的add函式示例,來詳細瞭解這個概念。
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
模式匹配
模式匹配是匹配特定型別表示式的過程。它只是一個簡化程式碼的技術。此技術可以實現到任何型別的型別類中。可以使用 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
保護
保護是一個與模式匹配非常相似的概念。在模式匹配中,我們通常匹配一個或多個表示式,但是我們使用保護來測試表達式的某些屬性。
雖然建議使用模式匹配而不是保護,但從開發人員的角度來看,保護更易讀且更簡單。對於第一次使用的人來說,保護看起來可能非常類似於 If-Else 語句,但它們的功能不同。
在下面的程式碼中,我們使用保護的概念修改了我們的階乘程式。
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)
在這裡,我們聲明瞭兩個由“|”分隔的保護,並從main呼叫fact函式。在內部,編譯器的工作方式與模式匹配的情況相同,以產生以下輸出:
The factorial of 5 is: 120
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)
遞迴函式
遞迴是一種函式反覆呼叫自身的的情況。Haskell 沒有提供任何機制來迴圈執行任何表示式超過一次。相反,Haskell 希望您將整個功能分解成不同的函式集合,並使用遞迴技術來實現您的功能。
讓我們再次考慮我們的模式匹配示例,我們在其中計算了一個數字的階乘。查詢數字的階乘是使用遞迴的經典案例。在這裡,您可能會問,“模式匹配與遞迴有什麼不同?”這兩者之間的區別在於它們的使用方式。模式匹配用於設定終止約束,而遞迴是函式呼叫。
在下面的示例中,我們同時使用了模式匹配和遞迴來計算 5 的階乘。
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 的庫函式,您會發現大多數庫函式都是以高階方式編寫的。
讓我們來看一個示例,我們將匯入一個內建的高階函式 map 並使用它來根據我們的選擇實現另一個高階函式。
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 "tutorialspoint.com"
在上面的示例中,我們使用了型別類Char的toUpper函式將我們的輸入轉換為大寫。在這裡,“map”方法將函式作為引數並返回所需的輸出。以下是它的輸出:
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts sh-4.3$ main "TUTORIALSPOINT.COM"
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