Trong một datakhai 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. Colourlà một kiểu và Greenlà một phương thức khởi tạo chứa giá trị của kiểu Colour. Tương tự, Redvà 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 RGBkhông phải là một giá trị - đó là một hàm lấy ba Ints và trả về một giá trị! RGBcó loại
RGB :: Int -> Int -> Int -> Colour
RGBlà 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 RGBcho 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 Colourbằ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ữ Strings, 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 SBTreechứa hai hàm tạo dữ liệu. Nói cách khác, có hai hàm (cụ thể là Leafvà Branch) sẽ xây dựng các giá trị của SBTreekiể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ữ Stringtheo 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 SBTreevà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 Boolnhư một đối số BTree, nó sẽ trả về kiểu BTree Bool, là cây nhị phân lưu trữ Bools. Thay thế mọi lần xuất hiện của biến kiểu abằng kiểu Boolvà bạn có thể tự mình thấy nó đúng như thế nào.
Nếu muốn, bạn có thể xem BTreedướ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 BTreelà 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 aloại. Định nghĩa của nó là
data Maybe a = Nothing
| Just a
Đây, Maybelà một hàm tạo kiểu trả về một kiểu cụ thể. Justlà một hàm tạo dữ liệu trả về một giá trị. Nothinglà 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, Justnhận một giá trị kiểu avà 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, Maybelấ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 Maybes - 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 Inthoặc Maybe a. Đó là bởi vì Maybelà 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 Intvà Maybe alà 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ể.)
Carvừ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ạoCarkiểu không có đối số, trong ví dụ thứ hai, nó có ba. Trong cả hai ví dụ, phương thứcCarkhở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).