Skip to main content

Bài 07- Giới thiệu về Type Classes

TÓM TẮT
Video bài giảng
Chúng tôi đang dịch thuyết minh bài giảng sang tiếng Việt
note

Đây là phần giới thiệu về khái niệm và cách sử dụng các Lớp Kiểu từ góc nhìn của người dùng. Có nghĩa là, khi chúng ta đang phát triển trong Haskell, có nhiều Lớp Lớp Kiểu và chúng ta muốn hiểu và sử dụng chúng.

Hai bài học tiếp theo, sau khi tìm hiểu về cách tạo Kiểu, chúng ta sẽ xem xét nó từ góc nhìn của Lớp Kiểu và trình tạo minh họa cụ thể.

Sự tuyệt vời của các Lớp Kiểu

Cho đến giờ, chúng ta đã học được rằng, khi định nghĩa một hàm, chúng ta chọn rằng nó có thể được sử dụng với một Kiểu cụ thể như sau:

sqr :: Int -> Int
sqr v = v * v

Điều này cung cấp rất nhiều sự an toàn bởi vì bằng cách đảm bảo rằng hàm của bạn chỉ nhận Kiểu Int, bất kể giá trị mà nó nhận, bạn có thể làm các phép toán với nó. Nhưng nhược điểm là bạn bị hạn chế sử dụng hàm đó chỉ với một Kiểu đó. Nếu bạn muốn dùng Kiểu Double hoặc Float, bạn phải định nghĩa lại nó với một tên khác như sqrDouble và sqrFloat.

Hoặc với một Kiểu đa hình như thế này:

fst :: (a, b) -> a
fst (x, _) = x

Điều này sẽ cung cấp rất nhiều tính linh hoạt vì bạn có thể sử dụng các giá trị thuộc bất kỳ Kiểu nào làm đầu vào, nhưng bạn sẽ mất tất cả sự an toàn khi sử dụng các Kiểu.

Vì vậy, chúng ta có một sơ đồ mô tả như thế này:


| X (Polym.)
|
Flexibility |
|
|
| X (Types)
|
------------------⟶
Safety

Các Lớp Kiểu (Type classes) là những gì bạn nhận được khi bạn là một nhà phát triển ngôn ngữ lập trình giỏi và muốn có sự linh hoạt khi có các Kiểu đa hình và sự an toàn khi sử dụng tất cả các Kiểu cùng một lúc.


| X (Polym.) X (Type Classes)
|
Flexibility |
|
|
| X (Types)
|
------------------⟶
Safety

Tóm lại, những gì Lớp Kiểu làm là cho phép bạn sử dụng các giá trị đa hình một cách hạn chế. Các giá trị có thể thuộc các Kiểu khác nhau, nhưng không phải tất cả chúng. Chỉ là một tập hợp con được cho phép. Điều này được gọi là đa hình Ad-hoc hoặc overloading, nhưng bạn không cần phải nhớ điều đó ngay bây giờ.

Bây giờ chúng ta đã biết tại sao các Lớp Kiểu lại tuyệt vời, hãy xem chúng thực sự là gì!

Các Lớp Kiểu là gì?

Nếu bạn gặp những người thuộc câu lạc bộ vẽ nâng cao, bạn sẽ biết họ có thể vẽ. Tại sao? Bởi vì đó là một trong những yêu cầu để được vào câu lạc bộ!

Các lớp học Kiểu giống như các câu lạc bộ mà các Kiểu có thể tham gia nếu chúng có những hành vi cụ thể. (Hành vi, trong ngữ cảnh này, có nghĩa là hàm.) Vì vậy, một Lớp Kiểu chỉ định một loạt các hàm và mỗi Kiểu thuộc về Lớp Kiểu có các định nghĩa riêng về các hàm đó.

Vì vậy, từ quan điểm của nhà phát triển sử dụng các Kiểu và Kiểu có sẵn:

Nếu bạn thấy rằng một Kiểu là một trường hợp của một Lớp Kiểu, thì bạn biết rằng nó thực hiện và hỗ trợ các hành vi (hàm) của Lớp Kiểu đó.

Ví dụ, Kiểu Bool. Để xem các Lớp Kiểu mà Kiểu Bool đó thuộc về, bạn có thể sử dụng :i lệnh (thông tin) trong ghci. Nếu chúng ta chạy :i Bool, chúng ta nhận được:

type Bool :: *
data Bool = False | True
-- Defined in ‘GHC.Types’
instance Eq Bool -- Defined in ‘GHC.Classes’
instance Ord Bool -- Defined in ‘GHC.Classes’
instance Enum Bool -- Defined in ‘GHC.Enum’
instance Show Bool -- Defined in ‘GHC.Show’
instance Read Bool -- Defined in ‘GHC.Read’
instance Bounded Bool -- Defined in ‘GHC.Enum’

Chúng ta sẽ tìm hiểu về type và data trong bài học tiếp theo. Vì vậy, nếu chúng ta bỏ qua hai dòng mã đầu tiên, chúng ta sẽ thấy một loạt các dòng có nội dung instance.... Những dòng đó cho chúng ta biết rằng Kiểu Bool là một trường hợp của Lớp Kiểu Eq, Lớp Kiểu Ord, v.v.

Vì vậy, Bool thực hiện các hàm của tất cả các Lớp Kiểu đó. Và, một cách tự nhiên, bây giờ chúng ta muốn biết những hành vi mà các Lớp Kiểu đó định nghĩa. Vậy hãy cùng tìm hiểu nhé!

Các Lớp Kiểu phổ biến

Bây giờ, chúng ta sẽ đi qua các Lớp Kiểu phổ biến nhất. Và tôi sẽ cho bạn biết chúng đại diện cho điều gì và hành vi chính của chúng. Nhưng bạn không cần phải ghi nhớ bất cứ điều gì về điều này. Sau bài học "tạo Lớp Kiểu", bạn sẽ có thể nhanh chóng kiểm tra mọi thứ tôi sẽ nói bài này. Ngoài ra, đừng lo lắng về các chi tiết. Chúng ta sẽ dành phần này và hai bài học nữa về hệ thống Kiểu. Sử dụng bài học này để bắt đầu phát triển ý tưởng về một Lớp Kiểu và để làm quen với những lớp tích hợp phổ biến nhất (thường là những lớp duy nhất bạn cần).

Lớp Kiểu Eq

Lớp Kiểu Eq là tất cả đặc tính về so sánh. Các Kiểu là trường hợp của Lớp Kiểu Eq có thể cho biết liệu hai giá trị của Kiểu có bằng nhau hay không bằng cách sử dụng các hàm == (bằng) và hàm /= (không bằng).

Và bởi vì Kiểu Bool là một trường hợp của Eq, nên chúng ta biết rằng chúng ta có thể sử dụng hai hàm đó để so sánh các giá trị của Kiểu đó:

True == False  -- False

True /= False -- True

Và nếu chúng ta kiểm tra khai báo của các hàm == và /=, chúng ta sẽ thấy một vài điều mới:

(==) :: Eq a => a -> a -> Bool

(/=) :: Eq a => a -> a -> Bool

Biểu tượng => là biểu tượng ràng buộc lớp. Nó chỉ ra rằng một Kiểu đa hình bị ràng buộc là một trường hợp của một Lớp Kiểu.

Mã ở bên phải của mũi tên mập ( =>) là cùng một Kiểu khai báo mà chúng ta đã sử dụng cho đến nay. Và mã bên trái của mũi tên mập ( =>) chỉ ra các ràng buộc của lớp.

Trong trường hợp này, mã ở bên phải của mũi tên mập ( a -> a -> Bool) chỉ ra rằng các hàm này nhận hai giá trị đa hình và trả về a Bool. Như mọi khi. Và mã ở bên trái mũi tên mập ( Eq a) chỉ ra rằng Kiểu a được sử dụng hai lần ở bên phải mũi tên mập phải là một trường hợp của Lớp Kiểu Eq.

Vì vậy, chúng ta đang hạn chế (giới hạn) các Kiểu mà bạn có thể chuyển đến hai hàm này, tất cả các Kiểu đến chỉ là những Kiểu thuộc trường hợp của Lớp Kiểu Eq.

Và nó không dừng lại ở đó. Ví dụ, hãy tưởng tượng bạn tạo hàm này:

func x y = if x == y then x else y

Bạn không làm toán hoặc thao tác với các chuỗi. Nhưng bạn kiểm tra xem các giá trị có bằng nhau không. Vì vậy, bạn muốn đảm bảo rằng hàm này chỉ chấp nhận các giá trị có thể được kiểm tra tính so sánh. Đó là những gì Eq ràng buộc Lớp Kiểu dành cho. Để chặn bạn sử dụng các Kiểu có giá trị không thể so sánh được.

Và bởi vì == có Eq a ràng buộc và func sử dụng == bên trong, Haskell đủ thông minh để suy ra rằng khai báo Kiểu hàm của chúng ta cũng có ràng buộc đó:

func :: Eq a => a -> a -> a
func x y = if x == y then x else y

Và bây giờ đến thời điểm của sự thật. Tôi có thể áp dụng các hàm này cho bao nhiêu Kiểu? chúng ta biết rằng chúng ta có thể áp dụng nó Bool vì Bool là một phiên bản của Eq. Nhưng còn những gì khác không? Đó là những trường hợp nào khác?

Chà.. nếu bạn sử dụng :i Eq lệnh, bạn sẽ thấy một danh sách lớn tất cả các Kiểu là trường hợp của Lớp Kiểu này:

--...
instance Eq a => Eq [a] -- Defined in ‘GHC.Classes’
instance Eq Word -- Defined in ‘GHC.Classes’
instance Eq Ordering -- Defined in ‘GHC.Classes’
instance Eq Int -- Defined in ‘GHC.Classes’
instance Eq Float -- Defined in ‘GHC.Classes’
instance Eq Double -- Defined in ‘GHC.Classes’
instance Eq Char -- Defined in ‘GHC.Classes’
instance Eq Bool -- Defined in ‘GHC.Classes’
--... more instances

Như bạn có thể thấy, tất cả các Kiểu mà chúng ta đã gặp cho đến nay (và hơn thế nữa) đều là các trường hợp của Lớp Kiểu này (ngoại trừ các hàm). Đó là lý do tại sao chúng ta có thể kiểm tra xem hai giá trị Kiểu CharIntFloat v.v. có bằng nhau hay không và đó là lý do tại sao chúng ta có thể áp dụng hàm func vừa định nghĩa cho bất kỳ giá trị nào trong số chúng:

func True False -- False

func 1 2 -- 2

func 1.0 1.0 -- 1.0

func 'a' 'c' -- 'c'

Và nếu bạn tình cờ chuyển một giá trị không phải là phiên bản của Eq, chẳng hạn như một hàm:

f1 x = x + 1
f2 x = x + 2 - 1

func f1 f2

Bạn sẽ gặp lỗi:

No instance for (Eq (Integer -> Integer))
arising from a use of ‘==’
(maybe you haven't applied a function to enough arguments?)

Bởi vì, giống như lỗi nói, Kiểu Integer -> Integer không phải là phiên bản của Eq, và chúng ta cần phải như vậy vì chúng ta đang sử dụng ==.

Điều đó thực sự tuyệt, nhưng bạn không thể làm được gì nhiều với các Kiểu chỉ thuộc về Lớp Kiểu Eq. Bạn chỉ có thể biết liệu hai giá trị có bằng nhau hay không. Đó là nó. May mắn thay, Eq không phải là Lớp Kiểu duy nhất có thể sử dụng!

Lớp Kiểu Ord

Lớp Kiểu Ord là tất cả mọi thứ về sắp xếp thứ tự. Các Kiểu là trường hợp của Lớp Kiểu Ord có thể sắp xếp các giá trị của chúng và cho biết giá trị nào là lớn nhất.

Và để làm được điều đó, Ord lớp có tất cả các hàm sau:

  (<), (<=), (>=), (>) :: Ord a => a -> a -> Bool
max, min :: Ord a => a -> a -> a
compare :: Ord a => a -> a -> Ordering

Chúng ta đã sử dụng các toán tử bất đẳng thức ( <><=>=) trong các bài học trước. Chúng lấy hai giá trị cùng Kiểu thuộc về Lớp Kiểu Ord và trả về một giá trị boolean:

4 > 9      -- False

'a' >= 'b' -- False

Và các giá trị được sắp xếp như thế nào? Nó phụ thuộc vào Kiểu. Với các số, nó tuân theo thứ tự toán học (ví dụ: 4 đến trước 5 và sau 3). Với các ký tự, nó tuân theo thứ tự Unicode. Và các Kiểu khác có thứ hạng khác. Như chúng ta đã nói, mỗi Kiểu (Type) thuộc về một Lớp Kiểu (Type Class) có các triển khai (định nghĩa) riêng của các hàm đó. Chúng ta sẽ tìm hiểu thêm về nó khi tạo các phiên bản của riêng mình.

Nhưng với khả năng sắp xếp mọi thứ xung quanh, chúng ta có thể làm được nhiều điều hơn là chỉ có sự bất so sánh.

Các hàm min và max

Hàm min nhận hai giá trị của một Kiểu là một trường hợp của Ord và trả về giá trị nhỏ nhất trong hai giá trị:

min :: Ord a => a -> a -> a

Ví dụ:

min 12 19 -- 12

Hàm max nhận hai giá trị của một Kiểu là một trường hợp của Ord và trả về giá trị lớn nhất trong hai giá trị:

max :: Ord a => a -> a -> a

Ví dụ:

max 12 19 -- 19

Hàm compare

Hàm compare nhận hai giá trị của một Kiểu là một trường hợp của Ord và trả về một giá trị của Kiểu Ordering, cho biết thứ tự của các giá trị.

compare :: Ord a => a -> a -> Ordering

Theo cùng một cách Bool chỉ có hai giá trị ( True và False), Kiểu Ordering chỉ có ba giá trị: LT (nhỏ hơn), EQ (bằng) và GT (lớn hơn).

Ví dụ:

compare 4 9          -- LT (4 is lesser than 9)

'f' `compare` 'e' -- GT ('f' is greater than 'e')

True `compare` True -- EQ ( True is equal to True)

Một lần nữa, cho đến nay, tất cả các Kiểu chúng ta đã học đều là trường hợp của Lớp Kiểu này (ngoại trừ các hàm).

Bây giờ, bạn có thể nói: "Nếu tôi có thể kiểm tra Eq với Lớp Kiểu Ord, tại sao tôi cần Lớp Kiểu Eq?"

Đôi khi một Kiểu trước hết phải là một trường hợp của một Lớp Kiểu này thì mới được phép trở thành một trường hợp của một Kiểu khác. Giống như bạn phải tham gia câu lạc bộ vẽ nguệch ngoạc để được phép đăng ký vào câu lạc bộ vẽ.

Đó là trường hợp với Eq và Ord.

Để sắp xếp thứ tự các giá trị của một Kiểu, đối với người mới bắt đầu, bạn phải biết liệu chúng có bằng nhau hay không. Điều này cho chúng ta biết rằng nếu chúng ta có một Kiểu là một trường hợp của Ord, thì nó cũng hỗ trợ tất cả các hành vi Eq! Trong những trường hợp này, ta nói đó Eq là lớp cha của Ord (ngược lại, Ord là lớp con của Eq).

Một lần nữa, bạn không cần phải ghi nhớ tất cả những điều này. Ban đầu, bạn sẽ có thể nhanh chóng kiểm tra nó và với một chút thời gian, bạn sẽ thuộc lòng tất cả các hành vi và phân lớp.

Điều gì đó tương tự cũng xảy ra với các Lớp Kiểu số.

Lớp Kiểu Num

Các Kiểu số là một trong những Kiểu được sử dụng nhiều nhất trong bất kỳ ngôn ngữ lập trình nào. Nhưng không phải tất cả các Kiểu số đều có thể làm những việc giống nhau.

Các Kiểu là trường hợp của Lớp Kiểu Num có thể hoạt động giống như các số. Nhưng không giống như một tập hợp con số cụ thể. Lớp Kiểu Num định nghĩa hành vi mà tất cả các số nên có.

Ví dụ: các Kiểu là trường hợp của Lớp Kiểu này có thể được cộng, trừ hoặc nhân (trong số những thứ khác):

(+) :: Num a => a -> a -> a

(-) :: Num a => a -> a -> a

(*) :: Num a => a -> a -> a

Ví dụ:

5 - 1      -- 4

8.9 + 0.1 -- 9.0

'a' - 'b' -- ERROR! Char is not an instance of Num!

Bây giờ, hãy tưởng tượng tôi muốn tạo một hàm thực hiện một số phép toán:

add1 x = x + 1

Tôi không muốn chọn một Kiểu như là Int và chỉ cho phép các giá trị IntFloatDouble và Kiểu Integer có thể hoạt động hoàn toàn tốt! Nhưng, nếu không có ràng buộc, tôi có thể truyền vào bất kỳ Kiểu nào! kết quả của là 'a' + 'b' ? Hay True + False ? Nó sẽ không có ý nghĩa gì cả!

Vì chỉ những Kiểu là trường hợp của Lớp Kiểu Num mới có thể sử dụng +, và bởi vì FloatDoubleInt và Integer tất cả đều là trường hợp của Num, nên chúng ta có thể hạn chế hàm của mình như sau:

add1 :: Num a => a -> a
add1 x = x + 1

Nhưng hãy nhớ rằng nếu bạn không chắc chắn về khai báo Kiểu, hãy hỏi trình biên dịch! Nó biết rằng để sử dụng +, bạn phải là một trường hợp của Kiểu Num, vì vậy nó sẽ tự động suy ra khai báo Kiểu của add1! Cung cấp sự linh hoạt và bảo vệ chúng ta cùng một lúc.

Điều này thật tuyệt. Nhưng, đôi khi, chúng ta cần một cái gì đó cụ thể hơn.

Lớp Kiểu Integral

Lớp Kiểu Num bao gồm tất cả các số, nhưng Lớp Kiểu Integral chỉ các số nguyên (tất cả). Chẳng hạn như 4, nhưng không 4.3.

Integral là một câu nhóm giới hạn hơn Num. Trong số tất cả các Kiểu chúng ta đã thấy cho đến nay, chỉ có Int và Integer thuộc về Lớp Kiểu Integral.

Lớp Kiểu này định nghĩa nhiều hành vi, một trong những hàm nổi tiếng nhất của Integral là div.

div :: Integral a => a -> a -> a

Nó nhận hai giá trị của một Kiểu là một trường hợp của Integral và chia chúng, chỉ trả về phần nguyên của phép chia.

Ví dụ:

3 `div`  5    -- 0

div 5 2 -- 2

Và ngược lại, chúng ta có Lớp Kiểu Fractional.

Lớp Kiểu Fractional

Lớp Kiểu Fractionallà tất cả về số phân số. Các Kiểu là trường hợp của Lớp Kiểu Fractional có thể biểu diễn và sửa đổi các giá trị phân số.

Cho đến nay, hàm được sử dụng nhiều nhất mà các trường hợp của Lớp Kiểu Fractional là / :

(/) :: Fractional a => a -> a -> a

Phép chia rất mạnh mẽ. Không giống như div, chúng ta có thể đạt mức chính xác hơn về các giá trị của mình vì chúng ta đang sử dụng các số phân số. Và chỉ Float và Double là các trường hợp của Lớp Kiểu này.

Ví dụ:

10 / 5  -- 2.0

5 / 2 -- 2.5

10 / 3 -- 3.3333333333333335

Lưu ý rằng chúng ta chưa bao giờ phải chỉ định Kiểu giá trị số trong bất kỳ ví dụ nào cho đến nay. Đó là bởi vì, ví dụ, số 3 có thể là một giá trị thuộc Kiểu IntIntegerFloatDouble và bằng cách áp dụng một số hàm nhất định, chẳng hạn như /, trình biên dịch có thể tìm ra rằng chúng ta muốn nói đến giá trị 3 thuộc về một trong các Kiểu là trường hợp của Lớp Kiểu Fractional.

:t (10/3) -- (10/3) :: Fractional a => a

Lớp Kiểu  Show

Lớp Kiểu Show được sử dụng để chuyển đổi các giá trị thành String s có thể đọc được. Nó có 3 hành vi khác nhau, nhưng hành vi bạn sẽ thấy đi xem lại là chức show năng:

show :: Show a => a -> String

Hàm show trả về một String đại diện của bất kỳ Kiểu nào là một trường hợp của Lớp Kiểu Show. Ví dụ:

show (3 :: Int) -- "3"

show True -- "True"

Điều này thực sự hữu ích cho việc gỡ lỗi và in nhật ký.

Lớp Kiểu  Read

Lớp Kiểu Read cung cấp hành vi ngược lại của Lớp Kiểu Show. Có nghĩa là nó nhận a String và trả về một giá trị thuộc Kiểu chúng ta yêu cầu, nếu có thể. Hành vi thường được sử dụng nhất là hàm read:

read :: Read a => String -> a 

Ví dụ:

read "3" / 2  -- 1.5

read "True" || False -- True

read "[1,2,3]" :: [Int] -- [1,2,3]

Hãy nhớ rằng nếu hàm String không chứa giá trị hợp lệ hoặc hàm read không biết Kiểu cần được trả về, nó sẽ đưa ra một lỗi:

read "3" -- Doesn't know which numeric type. Exception.

read "Turue" :: Bool -- "Turue" is not a valid Bool value. Exception.

Bạn có thể tìm thấy mô tả chi tiết về một Lớp Kiểu nếu bạn tìm kiếm nó trên Hoogle: https://hoogle.haskell.org/.

Bây giờ, chúng ta hãy xem trình biên dịch suy ra các Kiểu như thế nào.

Kiểu hợp lệ chung nhất

khai báo của hàm này là gì?

fToC x = (x - 32)*5/9

Hàm fToC có thể có một vài Kiểu khác nhau. fToC :: Float -> Float, Ví dụ.

Tuy nhiên, trong khi thực hiện suy luận Kiểu, trình biên dịch không giả định gì và hạn chế Kiểu của hàm càng ít càng tốt. Cung cấp cho bạn ràng buộc chung nhất.

Hãy làm điều đó từng bước một.

Vì vậy, trong trường hợp này, hàm nhận một giá trị và trả về một giá trị. Vì vậy, khai báo chung nhất sẽ là một:

fToC :: a -> a  -- Intermediate step

Tuy nhiên, giá trị mà nó nhận phải là một Kiểu số (chúng ta đang áp dụng một số hàm toán học. -*, và /).

Nhưng Kiểu nào sẽ phù hợp? Sẽ là Num (vì toán tứ - và *) hay là Fractional (vì toán tứ /)?

Trong trường hợp này, tất cả các Kiểu số là một phần của Num, nhưng chỉ Float và Double là một phần của Fractional. Vì vậy, để đảm bảo hàm này luôn hoạt động, nó phải nhận Kiểu hạn chế nhất, nghĩa là Fractional :

fToC :: Fractional a => a -> a

Và đó là cách trình biên dịch suy ra Kiểu của biểu thức. Lưu ý rằng Kiểu thậm chí có thể cụ thể hơn, như Float -> Float hoặc Double -> Double. Nhưng điều đó sẽ giả định rằng bạn cần một Kiểu hạn chế hơn mà không có bằng chứng rõ ràng.

Cuối dùng, Kiểu hợp lệ chung nhất sẽ thắng.

Ok, vì vậy, cho đến bây giờ, chúng ta đã hạn chế nếu Kiểu đó là một trường hợp của một Lớp Kiểu cụ thể. Và chúng ta biết có thể có nhiều Lớp Kiểu chuyên biệt hơn (Fractional là Lớp Kiểu chuyên biệt hơn Kiểu Num).

Nhưng nếu chúng ta cần một Kiểu có nhiều khả năng hơn, cụ thể hơn thì sao?

Nhiều ràng buộc (Multiple constraints)

Đôi khi bạn cần các ràng buộc khác nhau cho các biến Kiểu khác nhau.

Hoặc biến cùng Kiểu với nhiều ràng buộc. Tất cả điều này có thể dễ dàng trường hợp trong Haskell.

Nhiều ràng buộc cho cùng một Kiểu biến

Thực hiện hàm này bỏ qua số 3:

skip3 x = if x == 3 then x+1 else x

x có thể thuộc bất kỳ Kiểu nào là một trường hợp của Eq (vì toán tử ==) và Num (vì toán tử + và vì chúng ta đang so sánh đầu vào với giá trị 3 thuộc về Lớp Kiểu Num).

Để chỉ định nhiều ràng buộc cho cùng một biến Kiểu, chúng ta phải bao quanh chúng bằng dấu ngoặc đơn và thêm dấu phẩy giữa chúng.

Giống như chúng là một tuple:

skip3 :: (Eq p, Num p) => p -> p

Bây giờ, biến Kiểu p phải là trường hợp Kiểu của cả hai Eq và Num. Và, tất nhiên, chúng ta có thể thêm nhiều ràng buộc hơn nếu cần.

Các ràng buộc cho nhiều biến Kiểu

Hãy tạo một hàm nhận hai giá trị và trả về 1 nếu giá trị đầu tiên lớn hơn giá trị thứ hai và 0 ngược lại:

isXBigger x y = if x > y then 1 else 0

Trong trường hợp này, x và y phải là phiên bản của Ord. Và giá trị trả về là một số thuộc Lớp Kiểu  Num.

Đặt cái này lại với nhau, khai báo Kiểu sẽ là:

isXBigger :: (Ord a, Num p) => a -> a -> p

Bây giờ, chúng ta hãy thực hành một chút. Điều gì về hàm này?:

mistery1 x y z = if x > y then z/2 else z

chúng ta so sánh x và y với >, vì vậy chúng phải là trường hợp của Lớp Kiểu Ord.

Và giá trị trả về được chia bằng cách sử dụng / một trong các đường dẫn if-else. Vì vậy, z phải là một ví dụ của Fractional.

mistery1 :: (Ord a, Fractional p) => a -> a -> p -> p

Và cuối cùng, ví dụ cuối cùng của chúng ta là một sửa đổi về mistery1 nơi chúng ta thêm 1 vào x trước khi so sánh nó với y :

mistery2 x y z = if x+1 > y then z/2 else z

Giống như trước. Nhưng bây giờ x và y cũng phải là một ví dụ Num để có thể sử dụng + :

mistery2 :: (Ord a, Num a, Fractional p) => a -> a -> p -> p

Như bạn có thể thấy, chúng ta có thể áp dụng bao nhiêu ràng buộc tùy ý.

Tất nhiên, trình biên dịch có thể suy ra Kiểug cho bạn (hầu hết các trường hợp). Nhưng bạn vẫn sẽ phải nhận thức được những gì đang diễn ra để giải thích và hiểu chúng một cách chính xác. Ngoài ra, viết Kiểu của hàm trước khi định nghĩa nó là một cách thực hành tốt và là một cách tuyệt vời để giảm bớt quá trình định nghĩa nó sau này.

Nguồn bài viết tại đây


Picture