Trong một data
khai báo, một hàm tạo kiểu là thứ ở bên trái của dấu bằng. (Các) hàm tạo dữ liệu là những thứ ở bên phải của dấu bằng. Bạn sử dụng các hàm tạo kiểu trong đó một kiểu được mong đợi và bạn sử dụng các hàm tạo dữ liệu khi một giá trị được mong đợi.
Công cụ tạo dữ liệu
Để làm cho mọi thứ trở nên đơn giản, chúng ta có thể bắt đầu với một ví dụ về một loại biểu thị màu sắc.
data Colour = Red | Green | Blue
Ở đây, chúng ta có ba hàm tạo dữ liệu. Colour
là một kiểu và Green
là một phương thức khởi tạo chứa giá trị của kiểu Colour
. Tương tự, Red
và Blue
đều là các hàm tạo xây dựng các giá trị kiểu Colour
. Mặc dù vậy, chúng tôi có thể tưởng tượng ra việc đẩy nó lên!
data Colour = RGB Int Int Int
Chúng ta vẫn chỉ có kiểu Colour
, nhưng RGB
không phải là một giá trị - đó là một hàm lấy ba Ints và trả về một giá trị! RGB
có loại
RGB :: Int -> Int -> Int -> Colour
RGB
là một phương thức tạo dữ liệu là một hàm nhận một số giá trị làm đối số của nó, sau đó sử dụng những giá trị đó để tạo một giá trị mới. Nếu bạn đã thực hiện bất kỳ lập trình hướng đối tượng nào, bạn nên nhận ra điều này. Trong OOP, các hàm tạo cũng nhận một số giá trị làm đối số và trả về một giá trị mới!
Trong trường hợp này, nếu chúng ta áp dụng RGB
cho ba giá trị, chúng ta sẽ nhận được một giá trị màu!
Prelude> RGB 12 92 27
#0c5c1b
Chúng tôi đã xây dựng một giá trị của kiểu Colour
bằng cách áp dụng hàm tạo dữ liệu. Một phương thức khởi tạo dữ liệu hoặc chứa một giá trị giống như một biến sẽ hoặc lấy các giá trị khác làm đối số của nó và tạo ra một giá trị mới . Nếu bạn đã từng lập trình trước đó, thì khái niệm này hẳn không còn xa lạ với bạn.
Nghỉ giải lao
Nếu bạn muốn xây dựng một cây nhị phân để lưu trữ String
s, bạn có thể tưởng tượng làm điều gì đó như
data SBTree = Leaf String
| Branch String SBTree SBTree
Những gì chúng ta thấy ở đây là một kiểu SBTree
chứa hai hàm tạo dữ liệu. Nói cách khác, có hai hàm (cụ thể là Leaf
và Branch
) sẽ xây dựng các giá trị của SBTree
kiểu. Nếu bạn không quen với cách hoạt động của cây nhị phân, chỉ cần tiếp tục ở đó. Bạn không thực sự cần biết cây nhị phân hoạt động như thế nào, chỉ biết rằng cây này lưu trữ String
theo một cách nào đó.
Chúng ta cũng thấy rằng cả hai hàm tạo dữ liệu đều có một String
đối số - đây là Chuỗi mà chúng sẽ lưu trữ trong cây.
Nhưng! Điều gì sẽ xảy ra nếu chúng ta cũng muốn có thể lưu trữ Bool
, chúng ta phải tạo một cây nhị phân mới. Nó có thể trông giống như sau:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Nhập các hàm tạo
Cả hai SBTree
vàBBTree
đều là các hàm tạo kiểu. Nhưng có một vấn đề rõ ràng. Bạn có thấy chúng giống nhau như thế nào không? Đó là dấu hiệu cho thấy bạn thực sự muốn có một tham số ở đâu đó.
Vì vậy, chúng tôi có thể làm điều này:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Bây giờ chúng ta giới thiệu một biến kiểu a
như là một tham số cho hàm tạo kiểu. Trong khai báo này, BTree
đã trở thành một hàm. Nó nhận một kiểu làm đối số của nó và nó trả về một kiểu mới .
Điều quan trọng ở đây để xem xét sự khác biệt giữa một là loại bê tông (ví dụ bao gồm Int
, [Char]
và Maybe Bool
) mà là một loại có thể được gán cho một giá trị trong chương trình của bạn, và một chức năng loại constructor mà bạn cần để nuôi một loại để có thể được được gán cho một giá trị. Một giá trị không bao giờ có thể thuộc loại "danh sách", vì nó cần phải là "danh sách của một cái gì đó ". Theo tinh thần tương tự, một giá trị không bao giờ có thể thuộc loại "cây nhị phân", bởi vì nó cần phải là "cây nhị phân lưu trữ một cái gì đó ".
Nếu chúng ta chuyển vào, chẳng hạn Bool
như một đối số BTree
, nó sẽ trả về kiểu BTree Bool
, là cây nhị phân lưu trữ Bool
s. Thay thế mọi lần xuất hiện của biến kiểu a
bằng kiểu Bool
và bạn có thể tự mình thấy nó đúng như thế nào.
Nếu muốn, bạn có thể xem BTree
dưới dạng một hàm với loại
BTree :: * -> *
Các loại có phần giống như các loại - *
biểu thị một loại bê tông, vì vậy chúng tôi nói BTree
là từ một loại bê tông thành một loại bê tông.
Kết thúc
Hãy quay lại đây một chút và ghi lại những điểm tương đồng.
Các hàm tạo dữ liệu với các tham số rất tuyệt nếu chúng ta muốn có những thay đổi nhỏ trong các giá trị của mình - chúng tôi đặt các biến thể đó vào các tham số và để người tạo ra giá trị quyết định họ sẽ đưa vào đối số nào. Theo nghĩa tương tự, hãy nhập các hàm tạo có tham số là tuyệt vời nếu chúng ta muốn có những biến thể nhỏ trong các loại của mình! Chúng tôi đặt những biến thể đó làm tham số và để người tạo kiểu quyết định những đối số mà họ sẽ đưa vào.
Một trường hợp nghiên cứu
Như phần mở rộng nhà ở đây, chúng ta có thể xem xét Maybe a
loại. Định nghĩa của nó là
data Maybe a = Nothing
| Just a
Đây, Maybe
là một hàm tạo kiểu trả về một kiểu cụ thể. Just
là một hàm tạo dữ liệu trả về một giá trị. Nothing
là một hàm tạo dữ liệu có chứa một giá trị. Nếu chúng ta nhìn vào loại Just
, chúng ta thấy rằng
Just :: a -> Maybe a
Nói cách khác, Just
nhận một giá trị kiểu a
và trả về giá trị kiểu Maybe a
. Nếu chúng ta nhìn vào loại Maybe
, chúng ta thấy rằng
Maybe :: * -> *
Nói cách khác, Maybe
lấy một kiểu cụ thể và trả về một kiểu cụ thể.
Một lần nữa! Sự khác biệt giữa một loại bê tông và một hàm xây dựng loại. Bạn không thể tạo danh sách các Maybe
s - nếu bạn cố gắng thực thi
[] :: [Maybe]
bạn sẽ gặp lỗi. Tuy nhiên, bạn có thể tạo một danh sách Maybe Int
hoặc Maybe a
. Đó là bởi vì Maybe
là một hàm tạo kiểu, nhưng một danh sách cần phải chứa các giá trị của một kiểu cụ thể. Maybe Int
và Maybe a
là các kiểu cụ thể (hoặc nếu bạn muốn, các lệnh gọi hàm tạo kiểu trả về kiểu cụ thể.)
Car
vừa là phương thức khởi tạo kiểu (ở bên trái=
) vừa là phương thức tạo dữ liệu (ở bên phải). Trong ví dụ đầu tiên, hàm tạoCar
kiểu không có đối số, trong ví dụ thứ hai, nó có ba. Trong cả hai ví dụ, phương thứcCar
khởi tạo dữ liệu nhận ba đối số (nhưng loại của các đối số đó trong một trường hợp là cố định và trong trường hợp khác được tham số hóa).