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" 

在上面的示例中,我們使用了型別類ChartoUpper函式將我們的輸入轉換為大寫。在這裡,“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
廣告