Một đặc tính quan trọng của hệ thống kiểu (Type) của Haskell, khác biệt với các ngôn ngữ lập trình khác, là ở tính đa hình. Kiểu đa hình thông thường là parametric polymorphism, còn với haskell, có ad hoc polymorphism, hay còn gọi là overloading. Dưới đây là một số ví dụ:
• Các literal 1,2 vừa biểu diễn số nguyên cố định, vừa biểu diễn số nguyên bất kỳ
• Toán tử so sánh == trong haskell được dùng trên nhiều kiểu số, và trên nhiều kiểu khác
Cách thức overload là khác nhau với các kiểu (Type) khác nhau, trong khi parametric polymorphism, các kiểu không gây ảnh hưởng gì. Trong Haskell, type classes cung cấp một cách có cấu trúc để điều khiển ad hoc polymorphism, hay overloading.
Ví dụ, với toán tử so sánh ==, cần được định nghĩa với một số types, nhưng với một số khác lại không. Ví dụ, so sánh == giữa các functions thường được xem là không cần thiết, nhưng lại cần so sánh == giữa các list. Xét định nghĩa của hàm
elem, thực hiện kiểm tra một phần tử có thuộc 1 list hay không:
Như vậy, Type của elem là a [a] Bool. Điều này cũng có nghĩa là == có type là a [a] Bool, mặc dù chúng ta không mong muốn == được định nghĩa cho tất cả các types
Hơn nữa, như đã nói ở trên, ngay cả khi == được định nghĩa cho tất cả các types, so sánh == giữa 2 lists khác biệt rất lớn so với so sánh giữa 2 số nguyên. Trong trường hợp này, chúng ta mong muốn == được overloaded để có thể thực hiện được nhiều task khác nhau.
Type Classes có thể dễ dàng giải quyết các vấn đề trên, nó cho phép định nghĩa types nào là instances của class nào, và tạo các định nghĩa của các overloaded
Ở đây Eq là tên của class được định nghĩa, và == là toán tử đơn trong class. Định nghĩa này có thể được hiểu là: “type a là một instance của class Eq nếu có một (overloaded) operation == của type phù hợp, định nghĩa trên nó” (Chú ý == chỉ được định nghĩa trên các cặp object của cùng một type)
Ràng buộc một type a là một instance của class Eq được ký hiệu: Eqa. Do đó Eq a không phải là một type expression, mà biểu diễn một ràng buộc trên 1 type, và được gọi là một context. Contexts được đặt ở trước của các type expressions. Ví dụ, kết quả của class declaration ở trên là gán type sau cho ==:
Phần code trên có thể hiểu như sau: với mỗi type a là instance của class Eq, == có type là a a Bool.
Dưới đây là type được sử dụng cho == trong ví dụ về elem
Đoạn code được hiểu là, Với tất cả type a là instance của class Eq, elem có kiểu là a [a] Bool. Đây chính là điều chúng ta mong đợi, nó thể hiện elem không được định nghĩa trên tất cả các type, chỉ được định nghĩa cho những type mà ta biết cách thức so sánh các elements của nó.
Tuy nhiên, vấn đề đặt ra là cách thức nào để chỉ ra những type nào là instances của class Eq, và behaviour của == trên mỗi types đó là gì? Điều này được thực hiện với instance declaration. Ví dụ:
Định nghĩa của == được gọi là 1 method. Hàm integerEq làm hàm so sánh == giữa các số nguyên, nhưng nói chung, bất kỳ biểu thức nào cũng có thể được phép trong vế phải, cũng như định nghĩa của bất kỳ định nghĩa hàm nào khác. Toàn bộ khai báo ở trên có thể được hiểu như sau: type integer là một instance của class
Eq, và đây là định nghĩa của method tương ứng với toán tử ==. Như vậy với khai báo này, chúng ta có thể so sánh == giữa các số nguyên, tương tự, khi so sánh == gữa các số thực
Các type đệ quy như Tree cũng có thể được xử lý như sau:
Chú ý rằng context Eq a trong dòng đầu tiên là cần thiết, vì các phần tử lá (kiểu a) được so sánh == trong dòng thứ hai. Ngoài ra, ràng buộc thêm vào cho biết chúng ta có thể so sánh == cho cây các a nếu như chúng ta biết cách so sánh == với a. Nếu context Eq a được bỏ khỏi khai báo trên, kết quả trả về sẽ là type error. Haskell report, đặc biệt là Prelude, chứa số lượng khá lớn các ví dụ hữu ích về các type class. Class Eq được định nghĩa rộng hơn một chút so với định nghĩa Eq ở trên:
Đoạn code là một ví dụ về một class với 2 operations, thứ là toán tử ==, thứ hai là toán tử khác: /=. Điều này minh họa việc sử dụng của một default method, trong trường hợp này là cho toán tử /=.
Haskell cũng hỗ trợ class extension. Ví dụ, có thể định nghĩa một lớp Ord kế thừa tất cả các operations trong Eq, và thêm vào đó là tập các toán tử so sánh và các hàm lấy max, lấy min:
Cần chú ý context trong khai báo class. Chúng ta nói Eq là một superclass của Ord (và ngược lại, Ord là subclass của Eq), đồng thời bất cứ type nào là một instance của Ord cũng là instance của Eq.
Kế thừa class như vậy còn đem lợi ích cho việc biểu diễn context được ngắn gọn hơn: một type expression cho một function sử dụng các toán tử của cả class Eq và Ord có thể sử dụng context (Ord a), hơn là (Eq a, Ord a) vì Ord có thể được hiểu cũng là một Eq. Quan trọng hơn, các methods cho các toán tử của subclass có thể giả thiết sự tồn tại của các method cho các toán tử của superclass. Ví dụ, khai báo Ord trong Prelude chuẩn đã có định nghĩa method mặc định cho toán tử (<)
Haskell cũng hỗ trợ đa thừa kế, một class có thể có nhiều hơn một superclass. Ví dụ, khai báo:
tạo một class C kế thừa các operation từ cả Eq và Show.
Các Class method được xem như các khai báo mức cao nhất trong Hakell. Chúng chia sẻ chung cùng một namspace các bieesn thông thường, một name có thể được sử dụng để bao hàm cả một class method và một biến, hay một method trong các class khác nhau.
Context cũng được sử dụng trong khai báo data, tương tự như những phần đã trình bày ở trên.
Các class method có thể có thêm các ràng buộc class trên bất kỳ type variable nào trừ các type variable được định nghĩa trong class đang xét. Ví dụ, trong class:
method m đòi hỏi type b phải thuộc class Show. Tuy nhiên, method m không thể đặt thêm bất kỳ ràng buộc class nào vào type a. Thay vào đó, những ràng buộc này phải là một phần của context trong khai báo class.
Trong các trường hợp xét đến ở trên, chúng ta mới chỉ sử dụng các type dạng “first-order”. Ví dụ, type constructor Tree chỉ đi cùng với một tham số, như
Tree Integer (một cây chứa các giá trị nguyên) hay Tree a (Biễu diễn tập các tree chứa các giá trị a). Nhưng Tree thực chất chính là một type constructor, và nhận type như một tham số đầu vào và trả về một type như một kết quả. Không có
giá trị nào trong Haskell có kiểu type như thế, nhưng các “higher-order type như vậy có thể được sử dụng trong các khai báo class
Xét ví dụ sau về class Functor (Lấy từ Prelude)
Hàm fmap là tổng quát hóa của hàm map đã sử dụng ở các ví dụ trước. Chú ý rằng type variable f được áp dụng cho các type khác trong f a và f b. Do đó, ta trông đợi nó được gắn với một type như Tree để có thể được xem như một tham số đầu vào. Một instance của Functor cho type Tree sẽ là:
Khai báo instance trên cho biết Tree, chứ không phải là Tree a, là một instance của Functor. Khả năng này là khá hữu ích.
So sánh với các ngôn ngữ khác. Các Class được sử dụng trong haskell cũng tương tự như các Class trong các ngôn ngữ lập trình hướng đối tượng khác như C+ + và Java. Tuy nhiên, có một số điểm khác biệt quan trọng như:
Haskell tách biệt định nghĩa của một type với định nghĩa của các method kết hợp với type đó. Trong khi đó, một Class trong Java hay C++ thường định nghĩa cả một cấu trúc dữ liệu (các variable-field) và các hàm kết hợp với cấu trúc đó (các method). Trong haskell, các định nghĩa này được tách riêng ra.
Các methods của các Class trong Haskell tương đương với các hàm ảo (virtual functions) trong một Class của C++. Mỗi instance của một class cung cấp các định nghĩa của riêng nó cho mỗi method
Các class trong Haskell gần tương tự như các Interface trong Java. Giống như một khai báo interface, một khai báo Class trong haskell định nghĩa một giao thức cho việc sử dụng một object hơn là định nghĩa chính object Haskell không hỗ trợ kiểu C++ overloading, tức là các functions có các
kiểu khác nhau, nhưng lại có cùng một tên.
Type của một Haskell object không thể được ép kiểu một cách tường minh, không có loại class cơ sở Object cho mọi loại giá trị thuộc hay không thuộc về
C++ và Java gắn thông tin định danh (như VTable) vào runtime của một object. Nhưng trong Haskell, những thông tin như thế không được gắn một cách vật lý mà được gắn một cách logic vào các giá trị, thông qua hệ thống type
Không có điều khiển truy nhập (như là các thành phần public, hay private của một Class) trong hệ thống Class của Haskell. Thay vào đó, hệ thống module sẽ được sử dụng để che dấu hoặc đưa ra các component của một class.