Haskell - Chức năng

Các hàm đóng một vai trò quan trọng trong Haskell, vì nó là một ngôn ngữ lập trình hàm. Giống như các ngôn ngữ khác, Haskell có định nghĩa và khai báo chức năng của riêng nó.

  • Khai báo hàm bao gồm tên hàm và danh sách đối số của nó cùng với đầu ra của nó.
  • Định nghĩa hàm là nơi bạn thực sự xác định một hàm.

Chúng ta hãy lấy một ví dụ nhỏ về hàm add để hiểu chi tiết khái niệm này.

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 

Ở đây, chúng ta đã khai báo hàm của mình ở dòng đầu tiên và ở dòng thứ hai, chúng ta đã viết hàm thực của mình sẽ nhận hai đối số và tạo ra một đầu ra kiểu số nguyên.

Giống như hầu hết các ngôn ngữ khác, Haskell bắt đầu biên dịch mã từ phương thức chính . Mã của chúng tôi sẽ tạo ra kết quả sau:

The addition of the two numbers is:
7

Khớp mẫu

Khớp mẫu là quá trình đối sánh loại biểu thức cụ thể. Nó không là gì ngoài một kỹ thuật để đơn giản hóa mã của bạn. Kỹ thuật này có thể được triển khai vào bất kỳ loại lớp Type nào. If-Else có thể được sử dụng như một tùy chọn thay thế của đối sánh mẫu.

Đối sánh mẫu có thể được coi là một biến thể của đa hình động, trong đó tại thời gian chạy, các phương thức khác nhau có thể được thực thi tùy thuộc vào danh sách đối số của chúng. Hãy xem khối mã sau. Ở đây chúng tôi đã sử dụng kỹ thuật So khớp mẫu để tính giai thừa của một số.

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

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

Tất cả chúng ta đều biết cách tính giai thừa của một số. Trình biên dịch sẽ bắt đầu tìm kiếm một hàm được gọi là “fact” với một đối số. Nếu đối số không bằng 0, thì số sẽ tiếp tục gọi cùng một hàm với số ít hơn 1 so với đối số thực. Khi mẫu của đối số khớp chính xác với 0, nó sẽ gọi mẫu của chúng ta là “fact 0 = 1”. Mã của chúng tôi sẽ tạo ra kết quả sau:

The factorial of 5 is:
120

Lính canh

Bảo vệ là một khái niệm rất giống với đối sánh mẫu. Trong đối sánh mẫu, chúng tôi thường so khớp một hoặc nhiều biểu thức, nhưng chúng tôi sử dụng bảo vệ để kiểm tra một số thuộc tính của một biểu thức.

Mặc dù nên sử dụng đối sánh mẫu thay vì bảo vệ , nhưng từ quan điểm của nhà phát triển, bảo vệ dễ đọc và đơn giản hơn. Đối với những người sử dụng lần đầu, bảo vệ có thể trông rất giống với câu lệnh If-Else, nhưng chúng khác nhau về chức năng. Trong đoạn mã sau, chúng tôi đã sửa đổi chương trình giai thừa của mình bằng cách sử dụng khái niệm lính canh .

Lính canh
Bảo vệ là một khái niệm rất giống với đối sánh mẫu. Trong đối sánh mẫu, chúng tôi thường so khớp một hoặc nhiều biểu thức, nhưng chúng tôi sử dụng bảo vệ để kiểm tra một số thuộc tính của một biểu thức.
Mặc dù nên sử dụng đối sánh mẫu thay vì bảo vệ , nhưng từ quan điểm của nhà phát triển, bảo vệ dễ đọc và đơn giản hơn. Đối với những người sử dụng lần đầu, bảo vệ có thể trông rất giống với câu lệnh If-Else, nhưng chúng khác nhau về chức năng.
Trong đoạn mã sau, chúng tôi đã sửa đổi chương trình giai thừa của mình bằng cách sử dụng khái niệm lính canh .
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) 

Ở đây, chúng tôi đã khai báo hai lính canh , cách nhau bởi “|” và gọi hàm fact từ main . Bên trong, trình biên dịch sẽ hoạt động theo cách tương tự như trong trường hợp khớp mẫu để mang lại kết quả sau:

The factorial of 5 is:
120

Mệnh đề Where

Đâu là một từ khóa hoặc hàm có sẵn có thể được sử dụng trong thời gian chạy để tạo ra kết quả mong muốn. Nó có thể rất hữu ích khi việc tính toán hàm trở nên phức tạp.

Hãy xem xét một tình huống trong đó đầu vào của bạn là một biểu thức phức tạp với nhiều tham số. Trong những trường hợp như vậy, bạn có thể chia toàn bộ biểu thức thành các phần nhỏ bằng cách sử dụng mệnh đề “where”.

Trong ví dụ sau, chúng ta đang sử dụng một biểu thức toán học phức tạp. Chúng tôi sẽ chỉ cho bạn cách bạn có thể tìm nghiệm nguyên của phương trình đa thức [x ^ 2 – 8x + 6] bằng cách sử dụng Haskell.

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))

Lưu ý mức độ phức tạp của biểu thức của chúng ta để tính các nghiệm nguyên của hàm đa thức đã cho. Nó khá phức tạp. Do đó, chúng tôi đang phá vỡ biểu thức bằng cách sử dụng mệnh đề where . Đoạn mã trên sẽ tạo ra kết quả sau:

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

Hàm đệ quy

Đệ quy là một tình huống mà một hàm gọi chính nó lặp đi lặp lại. Haskell không cung cấp bất kỳ cơ sở lặp lại bất kỳ biểu thức nào nhiều hơn một lần. Thay vào đó, Haskell muốn bạn chia toàn bộ chức năng của mình thành một tập hợp các chức năng khác nhau và sử dụng kỹ thuật đệ quy để triển khai chức năng của bạn.

Chúng ta hãy xem xét lại ví dụ về đối sánh mẫu của chúng ta, nơi chúng ta đã tính giai thừa của một số. Tìm giai thừa của một số là một trường hợp cổ điển của việc sử dụng Đệ quy. Ở đây, bạn có thể, “So khớp mẫu khác với đệ quy như thế nào?” Sự khác biệt giữa hai điều này nằm ở cách chúng được sử dụng. So khớp mẫu hoạt động khi thiết lập ràng buộc đầu cuối, trong khi đệ quy là một lời gọi hàm.

Trong ví dụ sau, chúng tôi đã sử dụng cả đối sánh mẫu và đệ quy để tính giai thừa của 5.

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

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

Nó sẽ tạo ra kết quả sau:

The factorial of 5 is:
120

Chức năng đặt hàng cao hơn

Cho đến bây giờ, những gì chúng ta đã thấy là các hàm Haskell nhận một kiểu làm đầu vào và tạo ra một kiểu khác làm đầu ra, điều này khá giống với các ngôn ngữ mệnh lệnh khác. Các hàm thứ tự cao hơn là một tính năng độc đáo của Haskell nơi bạn có thể sử dụng một hàm làm đối số đầu vào hoặc đầu ra.

Mặc dù nó là một khái niệm ảo, nhưng trong các chương trình thế giới thực, mọi chức năng mà chúng ta xác định trong Haskell đều sử dụng cơ chế bậc cao hơn để cung cấp đầu ra. Nếu bạn có cơ hội xem xét hàm thư viện của Haskell, thì bạn sẽ thấy rằng hầu hết các hàm thư viện đã được viết theo cách thức cao hơn. Hãy để chúng tôi lấy một ví dụ trong đó chúng tôi sẽ nhập một bản đồ hàm bậc cao có sẵn và sử dụng bản đồ đó để triển khai một hàm bậc cao khác theo sự lựa chọn của chúng ta.

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 "dongthoigian.net" 

Trong ví dụ trên, chúng ta đã sử dụng hàm toUpper của Type Class Char để chuyển đầu vào của chúng ta thành chữ hoa. Ở đây, phương thức “map” đang nhận một hàm làm đối số và trả về kết quả đầu ra được yêu cầu. Đây là đầu ra của nó

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"dongthoigian.net" 

Lambda Expression

Đôi khi chúng ta phải viết một hàm chỉ được sử dụng một lần, trong toàn bộ vòng đời của một ứng dụng. Để đối phó với loại tình huống này, các nhà phát triển Haskell sử dụng một khối ẩn danh khác được gọi là biểu thức lambda hoặc hàm lambda .

Một hàm không có định nghĩa được gọi là hàm lambda. Một hàm lambda được biểu thị bằng ký tự “\”. Chúng ta hãy lấy ví dụ sau, nơi chúng ta sẽ tăng giá trị đầu vào lên 1 mà không cần tạo bất kỳ hàm nào.

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

Ở đây, chúng tôi đã tạo một hàm ẩn danh không có tên. Nó lấy số nguyên 4 làm đối số và in ra giá trị đầu ra. Về cơ bản chúng ta đang vận hành một hàm mà không cần khai báo nó đúng cách. Đó là vẻ đẹp của biểu thức lambda. Biểu thức lambda của chúng tôi sẽ tạo ra kết quả sau:

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

Haskell – Thêm về các chức năng (xem thêm)

Trả lời