Tải bản đầy đủ (.doc) (55 trang)

Tổng quan về ngôn ngữ lập trình haskell

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (978.91 KB, 55 trang )

Mục lục
1. Tổng quan về Haskell 3
1.1. Giới thiệu 3
1.2. Lịch sử của Haskell 3
Vì sao chúng ta cần đến Haskell? 5
Điểm yếu của Haskell? 5
1.3. Cấu trúc chương trình 5
2. Cấu trúc từ vựng và các khái niệm tổng quát trong Haskell 6
2.1. Quy ước ký hiệu 6
2.2. Cấu trúc từ vựng chương trình 7
2.3. Chú thích 8
2.4. Định danh và các toán tử 9
2.5. Chữ kiểu số 10
2.6. Kiểu ký tự và kiểu chuỗi 11
2.7. Khai báo và liên kết trong Haskell 12
2.8. Tổng quan về các kiểu và các lớp 13
2.8.1. Các loại 14
2.8.2. Cú pháp của kiểu 15
2.8.3. Cú pháp xác nhận lớp và ngữ cảnh 16
2.8.4. Ngữ nghĩa của kiểu và lớp 17
3. Giá trị, kiểu và các khái niệm 17
3.1. Kiểu đa hình 19
3.2. Các kiểu do người dùng tự định nghĩa 21
3.3. Kiểu đồng nghĩa 23
3.4. Các kiểu được xây dựng sẵn 24
3.4.1. Thể hiện danh sách và chuỗi số học 26
3.4.2. Chuỗi 26
4. Hàm trong Haskell 27
4.1 Trừu tượng hóa Lambda 28
4.2 Toán tử trung tố 28
4.2.1. Section 29


4.2.2. Khai báo cố định (fixity declarations) 29
4.3 Tính không chặt của hàm 30
4.4 Dữ liệu không giới hạn (Infinite data structure) 31
4.5 Hàm lỗi (error function) 32
5. Biểu thức theo trường hợp và khớp mẫu 33
5.1. Ngữ nghĩa của khớp mẫu 34
5.2. Ví dụ 35
5.3. Biểu thức trường hợp 36
5.4. Mẫu Lazy 37
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
5.5. Phạm vi từ vựng và các dạng lồng nhau 40
5.6.Trình bày 41
6. Các lớp kiểu và viết chồng 42
7. Các thao tác vào ra cơ bản trong Haskell 49
8. Những ưu điểm của Haskell 50
8.1. Haskell là một ngôn ngữ lập trình hàm thuần túy 50
8.2. Haskell được đánh giá là 1 trong những ngôn ngữ có syntax đẹp nhất 50
8.3. Tính biểu cảm cao 51
8.4. Tính sử dụng lại 52
8.5. Định trị trì hoãn 52
8.6. Khả năng trừu tượng hóa cao 53
9. Kết luận 53
Haskell đang trở thành xu thế 53
TÀI LIỆU THAM KHẢO 55
Trang 2
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
1. Tổng quan về Haskell
1.1. Giới thiệu
Haskell là một ngôn ngữ lập trình hàm thuần túy và mang tính trì hoãn. Sở dĩ
Haskell có tính chất trì hoãn bởi lẽ khi đi tìm câu trả lời cho 1 vấn đề, những tham

số không cần thiết sẽ không được định trị. Đối nghịch với tính chất trì hoãn là tính
chất tức thì – chiến lược định trị của đa phần các ngôn ngữ lập trình hiện nay (C,
C++, Java, thậm chí là ML – một ngôn ngữ lập trình hàm). Một ngôn ngữ có tinh
chất tức thì có nghĩa là mọi biểu thức đều được định trị cho dù kết quả đó có được
sử dụng hay không.
Haskell được gọi là ngôn ngữ lập trình hàm thuần túy bởi lẽ nó không cho phép
các hiệu ứng phụ - điều có thể làm thay đổi trạng thái của hệ thống. Việc một ngôn
ngữ lập trình không có hiệu ứng phụ sẽ không quá kinh khủng; Haskell sử dụng
một hệ thống đơn nguyên để tách biệt những tính toán không thuần túy ra khỏi
phần còn lại của chương trình để thực hiện chúng một cách an toàn. Chúng ta sẽ
nghiên cứu rõ hơn vấn đề này ở các phần tiếp theo về đơn nguyên và vào/ra trong
ngôn ngữ thuần túy.
Haskell được gọi là ngôn ngữ lập trình hàm bởi vì việc định trị một chương trình
tương đương với định trị một hàm trên khía cạnh toán học thuần túy. Điều này
khác biệt khi so sánh với các ngôn ngữ chuẩn (Ví dụ như C và Java) với cách định
trị một chuỗi các statements.
1.2. Lịch sử của Haskell
Lịch sử của Haskell được trích từ báo cáo Haskell 98 của chính các tác giả như
sau. Tháng 9 năm 1987, tại hội thảo về Các ngôn ngữ lập trình hàm và Kiến trúc
máy tính (FPCA ‘87) tại Portland, Oregon để bàn luận về một vấn đề trong cộng
đồng những người nghiên cứu về lập trình hàm: Có hơn một tá các ngôn ngữ lập
trình hàm thuần túy và tất cả chúng đều giống nhau về khả năng biểu diễn và nền
Trang 3
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
tảng ngữ nghĩa. Hội thảo đã đạt được sự nhất trí cao về việc thiếu một ngôn ngữ
phổ dụng đã cản trở sử dụng rộng rãi lớp ngôn ngữ lập trình hàm này. Hội nghị đã
quyết định thành lập một hội đồng để tạo ra ngôn ngữ đó, đồng thời nhanh chóng
kết nối các ý tưởng mới, là nền tảng vững chắc để phát triển các ứng dụng thực tế,
là phương tiện để khuyến khích mọi người sử dụng ngôn ngữ lập trình hàm. Tài
liệu này mô tả những nỗ lực của hội đồng, đó là một ngôn ngữ lập trình hàm thuần

túy gọi là Haskell. Tên gọi này được đặt theo tên của nhà logic học Haskell
B.Curry – người có những công trình là nền tảng logic cho ngôn ngữ.
Thành công chính của hội nghị là sáng tạo ra một ngôn ngữ thỏa mãn những
ràng buộc về:
 Thích hợp để giảng dạy, nghiên cứu, xây dựng các ứng dụng, bao gồm cả
việc xây dựng các hệ thống lớn.
 Có thể được mô tả hoàn toàn qua các công bố về cú pháp và ngữ nghĩa.
 Miễn phí: mọi người đều được phép cài đặt, xây dựng các hệ thống trên nền
tảng ngôn ngữ.
 Được xây dựng trên những ý tưởng có sự nhất trí cao.
 Làm giảm sự tràn lan của các ngôn ngữ lập trình hàm.
Hội đồng dự kiến rằng Haskell sẽ trở thành nền tảng cho những nghiên cứu về
thiết kế ngôn ngữ và hi vọng về sự mở rộng của thế giới ngôn ngữ cùng với các
kết quả thử nghiệm.
Quả thật, Haskell đã không ngừng mở rộng kể từ những công bố đầu tiên. Giữa
năm 1997 đã có 4 phiên bản thiết kế của ngôn ngữ này (bản mới nhất vào lúc đó
alf Haskell 1.4). Năm 1997, hội thảo Haskell tại Hà Lan, các nhà nghiên cứu đã
quyết định về phiên bản ổn định của Haskell – Đó là Haskell 98.
Haskell 98 được xem như là bản rút gọn của Haskell 1.4, được đơn giản hóa và
loại bỏ một số lỗi có thể mắc phải. Haskell được dự kiến sẽ là ngôn ngữ mang tính
ổn định theo nghĩa trong tương lai, bộ thực hiện cam kết sẽ hỗ trợ cho Haskell 98
chính xác như đã chỉ ra.
Trang 4
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Vì sao chúng ta cần đến Haskell?
Có rất nhiều lý do để sử dụng Haskell. Haskell là một trong những ngôn ngữ cho
phép tạo nên những đoạn mã lệnh trong thời gian ngắn nhất với ít lỗi nhất. Đồng
thời, chương trình viết bằng Haskell rất dễ đọc với khả năng mở rộng cao. Ví dụ:
factorial 1 = 1
factorial n = n * factorial (n-1)

Ví dụ trên thực hiện việc tính giai thừa, với cách viết gần gũi với tư duy đệ quy.
Tuy nhiên, có lẽ điều quan trọng nhất là những người sử dụng Haskell tạo nên
một cộng đồng với nhiều chia sẻ rất hữu ích. Hiện tại, Haskell vẫn luôn thay đổi
và những phản hồi của người sử dụng luôn được lưu ý để tạo nên thay đổi trong
các phiên bản mới.
Điểm yếu của Haskell?
Dưới đây là hai trong số những vấn đề những người sử dụng Haskell than phiền:
 Mã lệnh được sinh ra có vẻ chậm hơn các chương trình viết trên các ngôn
ngữ khác như C.
 Có vẻ khó debug hơn.
Vấn đề thứ hai không phải là vấn đề quá lớn bởi lẽ Haskell giúp tạo ra những
đoạn mã với ít lỗi. Vấn đề thứ nhất cũng khá phổ biến, tuy nhiên, thời gian tính
toán là rẻ hơn so với thời gian lập trình nên nếu phải đợi thêm 1 chút khi thực thi
để đổi đấy vài ngày debug thì cũng xứng đáng. Tuy vậy, vấn đề này xảy ra không
phải với mọi ứng dụng. Haskell có một thư viện các giao diện cho phép ghép nối
với những đoạn mã lệnh viết trên các ngôn ngữ khác khi cần tối ưu hóa thời gian
thực thi.
1.3. Cấu trúc chương trình
1. Ở mức cao nhất của 1 chương trình Haskell là tập hợp các modules. Các
modules này cung cấp phương thức điều khiển không gian tên phục vụ cho mục
đích sử dụng lại trong các chương trình lớn.
Trang 5
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2. Mức trên cùng của một module chứa một tập rất nhiều các kiểu khai báo. Các
khai báo này định nghĩa những thành phần sẽ được dùng sau đó ví dụ như các giá
trị nguyên thủy, kiểu dữ liệu, các lớp kiểu và các thông tin cố định.
3. Mức thấp hơn là các biểu thức – phần quan trọng nhất của lập trình Haskell.
Các biểu thức chỉ ra một giá trị và có một kiểu tĩnh.
4. Ở mức dưới cùng của Haskell là cấu trúc từ vựng. Cấu trúc từ vựng biểu diễn
cụ thể các chương trình Haskell dưới dạng text.

Ta sẽ nghiên cứu Haskell từ dưới lên
2. Cấu trúc từ vựng và các khái niệm tổng quát trong Haskell
2.1. Quy ước ký hiệu
Các quy ước ký hiệu để biểu diễn cú pháp của Haskell như sau:
[pattern] tùy chọn (có hoặc không)
{pattern} lặp hoặc không lặp
(pattern) nhóm
pat
1
|pat
2
lựa chọn
pat
<pat’>
hiệu – những thành phần sinh ra bởi pat mà
không được sinh ra bởi pat’
Cú pháp dạng tương tự BNF được sử dụng với các luật sản xuất có dạng sau:
Trang 6
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2.2. Cấu trúc từ vựng chương trình


Trang 7
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL

Kỹ thuật phân tích từ vựng sử dụng luật “nhai cực đại”: tại mỗi điểm, vị từ dài
nhất thỏa mãn luật sản xuất vị từ được đọc vào. Vì vậy, case là một từ được lưu lại
trong khi cases thì không. Tương tự như vậy, = được lưu nhưng == và ~= thì
không.
Mọi kiểu của các khoảng trống đều là ranh giới cho các vị từ. Các ký tự không

nằm trong kiểu ANY thì không phù hợp trong Haskell và sẽ gây ra lỗi.
2.3. Chú thích
Một chú thích thông thường sẽ bắt đầu bằng hai hay nhiều các gạch nối (ví dụ: )
và mở rộng sang cả dòng mới. Chuỗi tuần tự các gạch nối này không được phép
tạo thành một vị từ có nghĩa nào.
Ví dụ: “ >” hay “| “ không phải là bắt đầu của một chú thích bởi vì chúng đều
là các vị từ có nghĩa. Tuy vậy, “ foo” bắt đầu cho một chú thích.
Các chú thích lồng nhau được bắt đầu bởi “{-” và kết thúc bởi “-}”. Không có vị
từ có nghĩa nào bắt đầu bằng “{-” nên “{ ” sẽ bắt đầu cá chú thích lồng nhau
mặc dù các gạch nối được đặt lien tiếp.
Bản thân các chú thích sẽ không được phân tích về mặt từ vựng, kí hiệu “-}” sẽ
kết thúc chú thích lồng nhau. Các chú thích lồng nhau này có thể có độ sâu tùy ý,
mọi ký hiệu “{-“ sẽ bắt đầu cho một chú thích và sẽ được kết thúc bởi “-}”. Trong
các chú thích lồng nhau thì mỗi ký hiệu “{-” sẽ tương ứng với một kí hiệu “-}”.
Các chú thích lồng nhau này cũng được sử dụng cho trình biên dịch pragmas với
những chỉ thị cho trình biên dịch làm việc.
Trang 8
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2.4. Định danh và các toán tử
Một định danh bao gồm một chữ cái và kế đó là không hoặc nhiều các chữ cái,
số, dấu gạch dưới và dấu nháy đơn khác nữa. Các định danh được phân biệt về
mặt từ vựng thành 2 miền không gian tên: loại bắt đầu bằng chữ cái thường (định
danh kiểu biến) và loại bắt đầu bằng chữ cái viết hoa (định danh kiểu khởi tạo).
Các định danh trong Haskell có phân biệt chữ hoa và chữ thường: các định danh
name, naMe và Name là khác nhau (2 định danh đầu tiên là định danh kiểu biến,
định danh cuối cùng là định danh kiểu khởi tạo).
Dấu gạch dưới “_” được xem như 1 ký tự chữ thường và có thể xuất hiện ở mọi
vị trí của chữ thường. Tuy vậy, một số trình biên dịch đưa ra những chú ý cho
những định danh bắt đầu bởi “_”. Điều này cho phép các lập trình viên sử dụng
“_foo” như một tham số mà họ sẽ không sử dụng.

Các ký hiệu toán tử được tạo thành từ một hay nhiều các kí tự ký hiệu. Như đã
chỉ ra ở trên, chúng được phân biệt về mặt từ vựng thành 2 miền không gian tên:
 Một kí hiệu toán tử bắt đầu bởi 1 dấu “:” là một toán tử khởi tạo.
 Một kí hiệu toán tử bắt đầu bởi bất kỳ ký hiệu khác là một định danh thông
thường.
Trang 9
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Chú ý rằng bản thân dấu hai chấm được dành để khởi tạo danh sách trong
Haskell, điều này cũng giống như khi làm việc với các phần khác trong cú pháp
của danh sách, ví dụ như “[]” hay “[a, b]”.
Trong phần tiếp theo của báo cáo, 6 loại tên khác nhau sẽ được sử dụng:
varid variable
conid constructors
tyvar

varid type variables
tycon

conid type constructor
tycls

conid type classes
modid

conid modules
2.5. Chữ kiểu số
Có hai loại kiểu số khác nhau: đó là integer và float. Kiểu số nguyên thường được
cho dưới dạng số thập phân, hệ cơ số 8 (với tiền tố 0o hoặc 0O), hoặc hệ cơ số 16
(với tiền tố 0x hay 0X). Kiểu số thực luôn có dạng số thập phân và luôn có các chữ
số phía trước và sau dấu chấm (điều này giúp không gây nhầm lẫn khi sử dụng dấu

chấm).
Trang 10
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2.6. Kiểu ký tự và kiểu chuỗi
Trong Haskell, kiểu kí tự được viết trong dấu nháy đơn, ví dụ như ‘a’ và kiểu
chuỗi được viết trong dấu nháy kép, ví dụ như “Hello”. Chú ý, cần phải tránh dùng
dấu nháy đơn với kiểu kí tự, nhưng với kiểu chuỗi thì được. Tương tự như vậy,
dấu nháy kép có thể dùng được với ký tự nhưng với kiểu chuỗi thì nên tránh. \
luôn là bắt đầu của kí tự điều khiển. Loại charesc bao gồm những biểu diễn cho
các kí tự “alert” (\a), “backspace” (\b), “form feed” (\f), “new line” (\n), “carriage
return” (\r), “horizontal tab” (\t), and “vertical tab” (\v).
Một chuỗi có thể chứa một khoảng cách (hai dấu gạch chéo ngược bọc lấy các
khoảng trắng). Điều này cho phép ta viết các chuỗi dài trên nhiều dòng với một
dấu gạch chéo ở cuối dòng trên và một dấu gạch chéo ở đầu dòng dưới.
Trang 11
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2.7. Khai báo và liên kết trong Haskell
Phần này sẽ trình bày cú pháp và ngữ nghĩa của các khai báo trong Haskell
Các khai báo trong phần topdecls chỉ được phép ở mức trên cùng của một
module Haskell, trong khi đó decls có thể được dùng ở cả phần đầu hoặc trong các
vùng lồng nhau (trong các khối let hoặc where).
Để dễ trình bày, ta chia phần khai báo thành 3 nhóm:
 Kiểu dữ liệu do người dùng tự định nghĩa: chứa các khai báo về kiểu, kiểu
mới và dữ liệu. (phần 4.2)
Trang 12
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
 Các lớp kiểu và overloading: chứa các lớp, dẫn xuất và các khai báo chuẩn
khác. (phần 4.3)
 Các khai báo lồng nhau: chứa các kết nối dữ liệu, chữ ký kiểu và các khai
báo cố định. (phần 4.4)

Haskell có một vài kiểu dữ liệu nguyên thủy (như kiểu số nguyên, kiểu dấu phẩy
động), nhưng đa phần các kiểu dữ liệu được xây dựng với mã Haskell thông
thường sử dụng các khai báo kiểu và dữ liệu thông thường.
2.8. Tổng quan về các kiểu và các lớp
Haskell sử dụng hệ thống kiểu đa hình Hindley-Milner truyền thống để đưa ra ngữ
nghĩa kiểu tĩnh, nhưng hệ thống kiểu này được mở rộng thành các kiểu lớp (hay
các lớp) cung cấp một cấu trúc để đưa ra các hàm viết chồng nhau.
Một khai báo lớp đưa ra một kiểu lớp mới và các thao tác viết chồng. Bất kỳ dẫn
xuất nào của lớp cũng phải hỗ trợ đầy đủ các thao tác này. Một khai báo dẫn xuất
chỉ ra một kiểu là dẫn xuất của một lớp, chứa các định nghĩa của các thao tác được
viết chồng- gọi là các phương thức lớp- được kế thừa từ các kiểu đã được đặt tên.
Ví dụ: Giả sử ta cần viết chồng cho các thao tác (+) và negate với loại Int và
Float. Ta đưa ra 1 loại lớp mới gọi là Num:
Khai báo này được đọc là: “1 kiểu a là một dẫn xuất của lớp Num nếu tồn tại các
phương thức lớp (+) và negate cùng loại và được định nghĩa trên đó”.
Trang 13
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Ta cũng có thể khai báo Int và Float là các dẫn xuất của lớp này.
Với addInt, negateInt, addFloat, negate Float được cho trong trường hợp này là
các hàm nguyên thủy, nhưng trong trường hợp tổng quát thì chúng có thể là các
hàm được người dùng định nghĩa. Khai báo dẫn xuất đầu tiên được hiểu là “Int là
một dẫn xuất của lớp Num với những định nghĩa sau cho phương thức (+) và
negate”.
2.8.1. Các loại
Để đảm bảo tính đúng đắn của các biểu thức kiểu, chúng được chia thành các loại
khác nhau có dạng:

Ký hiệu * biểu diễn loại các khởi tạo kiểu rỗng

Nếu k

1
và k
2
là các loại thì k
1
 k
2
là loại của các kiểu có kiểu thuộc loại
k
1
và trả về kiểu thuộc loại k
2
Tham chiếu loại kiểm tra tính đúng đắn của biểu thức kiểu và giá trị kiểu. Tuy
nhiên, không giống như kiểu, loại hoàn toàn ngầm định và không thấy được trong
ngôn ngữ.
Trang 14
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
2.8.2. Cú pháp của kiểu
Đây là cú pháp cho biểu thức kiểu của Haskell. Các giá trị kiểu được xây dựng từ
các khởi tạo kiểu. Các khởi tạo kiểu bắt đầu bởi các chữ viết hoa. Dạng chính của
các biểu thức kiểu như sau:
1. Biến kiểu: được viết như các định danh với các chữ viết thường. Loại của 1
biến được ngầm xác định trong ngữ cảnh mà nó xuất hiện
2. Khởi tạo kiểu: Hầu hết các khởi tạo kiểu được viết như các định danh với chữ
viết hoa ở đầu. Ví dụ:
 Char, Int, Integer, Float, Double và Bool là các hằng số kiểu với loại *
 Maybe và IO là các khởi tạo kiểu và được xem như các kiểu với loại *  *
 Khởi tạo data T … hay newtype T … thêm khởi tạo kiểu T vào danh sách
các kiểu. Loại của T được xác định bởi các tham chiếu loại.
3. Ứng dụng kiểu: nếu t

1
là một kiểu của loại k
1
 k
2
và t
2
là một kiểu của loại
k
1
thì t
1
, t
2
là một biểu thức kiểu thuộc loại k
2
.
4. Một kiểu trong ngoặc có dạng (t) là đồng nhất với kiểu t.
Ví dụ: biểu thức kiểu IO a có thể được hiểu là ứng dụng của một hằng số IO với
biến a. Vì khởi tạo kiểu của IO có loại *  * nên biến a và cả biểu thức IO a đều
Trang 15
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
có loại *. Nói chung, ta cần tham chiếu loại để xác định loại thích hợp cho các
kiểu dữ liệu hay lớp mà người dùng tự định nghĩa.
Các cú pháp đặc biệt cho phép các biểu thức kiểu thể hiện dưới dạng truyền
thống:
1. Một kiểu hàm có dạng t1  t2, tương đương với kiểu () t1 t2. Ví dụ
Int  Int Float có nghĩa là Int  (Int Float)
2. Một kiểu bộ có dạng (t1,…, tk) với k lớn hơn 2 tương đương với (,…,) t1 …
tk chỉ kiểu bộ k với thành phần đầu tiên kiểu t1, thành phần thứ hai kiểu t2…

3. Một kiểu danh sách có dạng [t] tương đương với kiểu [ ] t, chỉ ra kiểu danh
sách các thành phần kiểu t.
2.8.3. Cú pháp xác nhận lớp và ngữ cảnh
Một xác nhận lớp có dạng qtycls tyvar chỉ ra sự tính bộ phận của kiểu tyvar trong
lớp qtycls. Một định danh lớp bắt đầu bằng một ký tự viết hoa. Một ngữ cảnh chứa
không hoặc nhiều các xác nhận lớp, có dạng tổng quát:
(C
1
u
1
, … , C
n
u
n
)
với C
1
,…,C
n
là các định danh lớp và các u
1
,…,u
n
là các biến kiểu hoặc ứng dụng
của biến kiểu với một hoặc một số kiểu. Trong trường hợp tổng quát, ta dùng cx
để biểu diễn một ngữ cảnh và ta viết cx => t để chỉ ra kiểu t nằm trong ngữ cảnh
cx. Ngữ cảnh cx chỉ được chứa các biến kiểu được tham chiếu trong t. Để thuận
tiện, ta viết cx => t ngay cả khi cx là rỗng.
Trang 16
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL

2.8.4. Ngữ nghĩa của kiểu và lớp
Hệ thống kiểu của Haskell luôn gán một kiểu cho mỗi biểu thức trong chương
trình. Trong trường hợp tổng quát, một kiểu có dạng
.u cx t∀ ⇒
với
u
là tập của
các biến kiểu u1,…,un. Trong các kiểu này, bất ky biến kiểu định lượng toàn cục
nào tự do trong cx cũng tự do trong t. Dưới đây là một số ví dụ về kiểu:
Trong kiểu thứ ba, ràng buộc Eq (f a) không thể đơn giản hơn vì f là định lượng
toàn cục.
Kiểu của biểu thức e phụ thuộc vào môi trường kiểu - nơi cung cấp kiểu cho
các biến tự do trong e, và môi trường lớp- nơi khai báo kiểu nào là dẫn xuất của
lớp nào (một kiểu trở thành một dẫn xuất của một lớp chỉ khi tồn tại một khai báo
dẫn xuất hay một mệnh đề kế thừa).
Kiểu tổng quát nhất có thể gán cho một biểu thức cụ thể (trong một môi trường
cho trước) gọi là kiểu chính. Hệ thống kiểu mở rộng Hindley-Milner của Haskell
có thể suy ra kiểu chính của tất cả các biểu thức, bao gồm cả những phương thức
lớp được viết chồng.
3. Giá trị, kiểu và các khái niệm
Vì Haskell là một ngôn ngữ lập trình hàm thuần túy nên tất cả các tính toán đều
được thực hiện thông qua việc định trị cho các biểu thức (các khái niệm mang tính
cú pháp) để sinh ra các giá trị (các thực thể trừu tượng mà ta coi là kết quả). Mọi
giá trị đều có một kiểu gắn với nó. (Bằng trực giác ta có thể hình dung kiểu như là
tập các giá trị). Các ví dụ của biểu thức chứa các giá trị nguyên tử như số nguyên
5, ký tự ‘a’, và hàm \x  x+1, và các giá trị có cấu trúc như danh sách [1,2,3] và
cặp (‘b’,4).
Trang 17
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Tương tự như biểu thức có giá trị, biểu thức kiểu có giá trị kiểu. Ví dụ về biểu

thức kiểu chứa các kiểu nguyên tử như kiểu Integer, Char, Integer  Integer, và
kiểu có cấu trúc [Integer], cặp (Char, Integer).
Tất cả các giá trị trong Haskell đều là “first-class”- chúng có thể được truyền
như là các đối số của các hàm, các kiểu trả về, trong các cấu trúc… Ngược lại, các
kiểu trong Haskell lại không phải là “first-class”. Kiểu là để mô tả giá trị và việc
gắn kết một giá trị với kiểu của nó gọi là định kiểu. Với các ví dụ về giá trị và kiểu
ở trên, ta có thể viết quá trình định kiểu như sau:
Ký hiệu “: :” được đọc là “có kiểu”.
Các hàm trong Haskell được định nghĩa là một dãy các phương trình. Ví dụ hàm
inc có thể được định nghĩa bởi một phương trình như sau:
Một phương trình là ví dụ của một khai báo. Một kiểu khác của khai báo là khai
báo chữ ký kiểu. Ta có thể khai báo việc định kiểu theo cách hiện của hàm inc như
sau:
Về mặt học thuật, khi ta muốn chỉ ra rằng một biểu thức e1 có giá trị hay “giảm
thành” biểu thức khác hay giá trị e2 , ta viết:
Ví dụ:
Hệ thống kiểu tính của Haskell định nghĩa mối quan hệ hình thức giữa kiểu và
giá trị. Hệ thống kiểu này đảm bảo các chương trình viết bằng Haskell là an toàn
về kiểu. Điều đó có nghĩa là trong một số trường hợp, lập trình viên sẽ không bị
Trang 18
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
nhầm kiểu. Ví dụ như chúng ta không thể cộng hai ký tự với nhau, vì thế biểu thức
‘a’+’b’ là sai về kiểu. Ưu điểm chính của các ngôn ngữ kiểu tĩnh đều được biết
đến: đó là tất cả các lỗi về kiểu được phát hiện trong quá trình biên dịch. Không
phải mọi lỗi đều do hệ thống kiểu. Một biểu thức, ví dụ như 1/0, có thể định kiểu
được nhưng việc định trị cho nó sẽ sinh ra lỗi trong quá trình thực thi. Hệ thống
kiểu luôn tìm thấy nhiều lỗi chương trình trong quá trình biên dịch giúp ích cho
người lập trình và đồng thời cho phép trình biên dịch tạo ra những mã hiệu quả
hơn (ví dụ như không cần quan tâm đến các lỗi về kiểu khi thực thi).
Hệ thống kiểu cũng đảm bảo rằng các chữ ký kiểu do người dùng cung cấp là

chính xác.Trên thực tế, hệ thống kiểu của Haskell đủ mạnh để để cho phép chúng
ta không cần viết bất kỳ một chữ ký kiểu nào; ta có thể nói rằng hệ thống kiểu này
suy dẫn ra kiểu chính xác. Tuy nhiên việc thay thế chữ ký kiểu cho hàm inc là một
ý tưởng hay, bởi lẽ chữ ký kiểu là một kiểu chú thích hiệu quả, giúp ích nhiều cho
việc debug sau này.
3.1. Kiểu đa hình
Haskell có các kiểu đa hình- các kiểu có thể được định lượng linh hoạt theo nhiều
kiểu khác nhau. Các biểu thức có kiểu đa hình được mô tả như một họ các kiểu. Ví
dụ
[ ]a a∀
là họ các kiểu (với mỗi a) chứa kiểu danh sách của a. Danh sách các số
nguyên (ví dụ [1,2,3]), danh sách các ký tự ([‘a’,’b’,’c’]), thậm chí là danh sách
của các danh sách số nguyên… là thành phần của họ này. (Chú ý rằng [2,’b’]
không phải là một ví dụ hợp lệ vì không có kiểu đơn nào chứ cả 2 và ‘b’).
Danh sách là cấu trúc dữ liệu được dùng phổ biến trong các ngôn ngữ lập trình
hàm và là phương tiện để diễn giải ý nghĩa của tính đa hình. Danh sách [1,2,3]
trong Haskell thực chất là viết tắt của danh sách 1:( 2:( 3:([])) với [] là danh sách
rỗng và : là toán tử trung tố làm nhiệm vụ cộng tham số đầu tiên vào phía trước
tham số thứ hai (1 danh sách). Ta cũng có thể viết như sau: 1:2:3:[].
Trang 19
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Lấy một ví dụ về một hàm được định nghĩa bởi người dùng làm việc trên danh
sách: tính số phần tử trong danh sách:
Ta có thể hiểu hàm này như sau: “Độ dài của một danh sách rỗng là 0 và độ dài
của một danh sách có phần tử đầu x và phần còn lại là xs là 1 cộng với độ dài của
xs”.
Mặc dù ví dụ này khá hiển nhiên nhưng nó đề cập đến một khái niệm mới: khớp
các mẫu. Vế trái của phương trình chứa các mẫu như [], x:xs. Trong một ứng dụng
hàm, các mẫu này được khớp với các tham số thực theo cách khá trực quan ([] chỉ
khớp với danh sách rỗng, x:xs khớp với danh sách có tối thiểu 1 thành phần và gán

x với phần tử đầu, xs với phần còn lại). Khi quá trình khớp này thành công, vế
phải sẽ được định trị và nếu tất cả các phương trình không đúng sẽ có lỗi xảy ra.
Định nghĩa các hàm thông qua việc khớp các mẫu là khá phổ biến trong Haskell
và người dùng phải làm quen với rất nhiều mẫu cho phép. Hàm xác định độ dài
dưới đây là một ví dụ về hàm đa hình. Nó có thể áp dụng cho một danh sách chứa
các thành phần có bất kỳ kiểu nào, ví dụ [Integer], [Char] hay [[Integer]].
Dưới đây là hai ví dụ nữa về hàm đa hình. Hàm head trả về thành phần đầu của
danh sách và hàm tail trả về các thành phần của danh sách sau phần tử đầu tiên.
Không giống hàm length, các hàm này không định nghĩa cho mọi trường hợp có
thể xảy ra của đối số vào. Lỗi thực thi sẽ xảy ra khi áp dụng hàm này với một danh
sách rỗng.
Trang 20
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Với các kiểu đa hình, ta có thể thấy một số kiểu là tổng quát hơn các kiểu khác
theo nghĩa là tập giá trị mà chúng định nghĩa lớn hơn. Ví dụ, kiểu [a] tổng quát
hơn kiểu [Char]. Nói cách khác, kiểu [Char] có thể được kế thừa từ kiểu [a] bằng
cách thay thế a thích hợp. Hệ thống kiểu của Haskell có hai tính chất quan trọng:
 Đầu tiên, mọi biểu thức được định kiểu đảm bảo chỉ có một kiểu chính duy
nhất.
 Sau đó, kiểu chính được suy dẫn tự động.
So sánh với ngôn ngữ chỉ có kiểu đơn hình như C, người đọc sẽ thấy tính đa
hình sẽ diễn tả diễn cảm hơn, và sự suy dẫn kiểu sẽ làm giảm gánh nặng về kiểu
cho người lập trình.
Kiểu chính của một biểu thức hay một hàm là kiểu ít tổng quát nhất thỏa mãn
“chứa tất cả các dẫn xuất của biểu thức”. Ví dụ như kiểu chính của head là [a]a;
ba, aa hoặc thậm chí là a nhưng quá tổng quát hoặc [Integer][Integer] là
quá cụ thể. Sự tồn tại duy nhất của các kiểu là tính chất của hệ thống kiểu
Hindley-Milner, nền tảng của hệ thống kiểu trong Haskell, ML, Miranda và hầu
hết các ngôn ngữ lập trình hàm khác.
3.2. Các kiểu do người dùng tự định nghĩa

Ta có thể tự định nghĩa các kiểu riêng trong Haskell với các khai báo dữ liệu. Một
kiểu quan trọng đã được định nghĩa trước trong Haskell là giá trị chân lý:
Kiểu được định nghĩa ở đây là Bool và nó có chính xác 2 giá trị: True và False.
Kiểu Bool là một ví dụ của khởi tạo kiểu. True và False là các khởi tạo dữ liệu
(hay nói ngắn gọn là khởi tạo).
Tương tự như vậy ta có thể định nghĩa một kiểu Color:
Trang 21
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Cả Bool và Color đều là các ví dụ về các kiểu liệt kê, chúng chứa một số lượng
xác định các khởi tạo dữ liệu.
Đây là ví dụ về một kiểu chỉ có một khởi tạo dữ liệu:
Do chỉ có một khởi tạo, kiểu giống như Point được gọi là kiểu bộ bởi vì bản chất
của nó là tích Đề-các của các kiểu khác. Ngược lại, các kiểu có nhiều khởi tạo như
Bool và Color được gọi là kiểu tổng hay kiểu hợp.
Quan trọng hơn là Point là ví dụ về kiểu đa hình: với mọi kiểu t, nó định nghĩa
kiểu các điểm Đề-các nhận t là kiểu cơ sở. Kiểu Point có thể được thấy như một
khởi tạo kiểu vì kiểu t tạo nên kiểu mới là Point t. (Cũng như vậy, sử dụng ví dụ
về danh sách ở trên, [] cũng là một khởi tạo kiểu. Với một kiểu t bất kỳ, ta có thể
“áp dụng” [] để tạo ra kiểu mới [t]. Cú pháp của Haskell cho phép viết [] t thành
[t]. Tương tự,  là một khởi tạo kiểu: cho 2 kiểu t và u, tu là kiểu của các hàm
ánh xạ các thành phần thuộc kiểu t thành các thành phần thuộc kiểu u).
Chú ý rằng kiểu của khởi tạo dữ liệu nhị phân Pt là aaPoint a, vì vậy các
định kiểu sau là đúng:
Mặt khác, biểu thức Pt ‘a’ 1 là không đúng vì ‘a’ và 1 là khác kiểu. Cần phải phân
biệt được giữa áp dụng khởi tạo dữ liệu để sinh ra giá trị và áp dụng khởi tạo kiểu
để sinh ra kiểu. Áp dụng khởi tạo dữ liệu để sinh ra giá trị xảy ra ở quá trình thực
hiện và đó là cách ta tính toán trong Haskell. Trong khi đó, áp dụng khởi tạo kiểu
để sinh ra kiểu xảy ra trong quá trình biên dịch và là một phần của quá trình xử lý
của hệ thống kiểu để đảm bảo an toàn kiểu.
Khởi tạo kiểu như Point và khởi tạo dữ liệu như Pt nằm trong các không gian

tên khác nhau. Điều này cho phép khởi tạo kiểu và khởi tạo dữ liệu như sau:
Trang 22
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Kiểu đệ quy: Các kiểu cũng có thể có tính chất đệ quy như trong kiểu của
cây nhị phân:
Ta đã định nghĩa một kiểu cây nhị phân đa hình với các thành phần là nút lá chứa
giá trị của kiểu a, một nút trong (các nhánh) chứa (đệ quy) 2 cây con
Khi đọc các khai báo dữ liệu, cần nhớ rằng Tree là 1 khởi tạo kiểu, trong khi đó
Branch và Leaf là các khởi tạo dữ liệu. Ngoài việc đưa ra các kết nối giữa các khởi
tạo, khai báo trên định nghĩa các kiểu sau:
Với ví dụ này, ta đã định nghĩa một kiểu đủ mạnh để định nghĩa một số hàm đệ
quy sử dụng nó. Giả sử ta muốn định nghĩa hàm fringe trả về danh sách các phần
tử tại nút lá của cây từ trái qua phải. Trong trường hợp này, ta thấy kiểu của hàm
là Tree a[a]: hàm đa hình mà với mỗi kiểu a, ánh xạ cây a thành một danh sách
a. Ta có thể định nghĩa như sau:
Ở đây ++ là toán tử trung tố giúp ghép 2 danh sách. Cũng giống ví dụ về hàm
length ở trên, hàm fringe được định nghĩa dựa trên việc khớp mẫu với các khởi tạo
do người dùng định nghĩa: Leaf và Branch.
3.3. Kiểu đồng nghĩa
Để thuận tiện khi làm việc, Haskell cung cấp cách định nghĩa các kiểu đồng nghĩa.
Các kiểu đồng nghĩa này được tạo ra bởi các khai báo kiểu. Dưới đây là một số ví
dụ:
Trang 23
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Các kiểu đồng nghĩa này không định nghĩa các kiểu mới mà chỉ đơn giản đặt tên
mới cho các kiểu đã có. Ví dụ kiểu Person  Name hoàn toàn tương đương với
(String, Address)String. Ngoài ưu điểm về tính ngắn gọn của tên mới so với tên
cũ, chúng còn cung cấp tính dễ đọc, dễ nhớ cho chương trình. Ta cũng có thể đặt
tên mới cho các kiểu đa hình:
Đây là kiểu danh sách kết hợp: ghép các giá trị thuộc kiểu a với các giá trị thuộc

kiểu b.
3.4. Các kiểu được xây dựng sẵn
Trong phần trước, ta đã giới thiệu một số kiểu được xây dựng sẵn trong Haskell
như danh sách, kiểu bộ, kiểu số nguyên, và kiểu ký tự. Ta cũng đã chỉ ra cách định
nghĩa các kiểu mới cho người sử dụng. Bên cạnh các cú pháp đặc biệt, một câu hỏi
đặt ra là các kiểu được xây dựng sẵn này có đặc biệt hơn các kiểu do người dùng
định nghĩa? Câu trả lời là KHÔNG. Các cú pháp đặc biệt không mang nhiều ngữ
nghĩa mà chỉ là các quy ước mang lại tính thuận tiện và nhất quán.
Ta có thể nghiên cứu cách thức định nghĩa các kiểu có sẵn dựa trên cú pháp đặc
biệt. Ví dụ, kiểu Char có thể được viết như sau:
Kiểu khai báo này không đúng về mặt cú pháp, ta cần phải sửa lại như sau:
Mặc dù khởi tạo này là chính xác hơn nhưng lại không thuận tiện để biểu diễn các
ký tự.
Trang 24
TIỂU LUẬN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH-NGÔN NGỮ HASKELL
Trong mọi trường hợp, các mã “pseudo-Haskell” như trên giúp ta hiểu rõ hơn
các cú pháp đặc biệt của Haskell. Cụ thể là ta có thể thấy Char là kiểu liệt kê chứa
rất nhiều các khởi tạo. Theo cách hiểu này, ta có thể khớp mẫu các ký tự khi định
nghĩa một hàm.
Ví dụ sau đây cũng chỉ ra cách sử dụng các chú thích trong Haskell như đã trình
bày ở trên. Ta định nghĩa kiểu Int và Integer như sau:
Kiểu Int có mức độ liệt kê lớn hơn kiểu Char nhưng vẫn rất rõ ràng! Mặc dù vậy,
pseudo-code cho kiểu Integer lại là kiểu liệt kê không xác định:
Tương tự như vậy, kiểu bộ cũng có thể được định nghĩa:
Mỗi định nghĩa trên khai báo một kiểu bộ với chiều dài cụ thể với dấu (…) đóng
vai trò trong cú pháp biểu thức (là khởi tạo dữ liệu) và cú pháp biểu thức kiểu (là
khởi tạo kiểu). Các dấu chấm dọc sau định nghĩa cuối cùng chỉ ra tính không xác
định của các khai báo.
Thực tế, danh sách rất dễ làm việc và có thể được định nghĩa:
[] là danh sách rỗng, “:” là khởi tạo danh sách trung tố, do đó [1,2,3] tương đương

với 1:2:3:[]. Kiểu của [] là a và kiểu của : là a ->[a]->[a].
Ở đây ta cần phân biệt giữa kiểu bộ và kiểu danh sách. Kiểu danh sách có bản
chất đệ quy với các thành phần đồng nhất như nhau và chiều dài là bất kỳ. Kiểu bộ
có bản chất không đệ quy với các thành phần hỗn hợp và chiều dài cố định. Quy
tắc định kiểu cho bộ và danh sách như sau:
 Với (e
1
, e
2
,…,e
n
), nếu e
1
có kiểu t
i
thì kiểu của bộ là (t
1
, t
2
,…,t
n
)
 Với [e
1
, e
2
,…,e
n
], nếu e
i

có kiểu t thì kiểu của danh sách [t]
Trang 25

×