8.1. Haskell là một ngôn ngữ lập trình hàm thuần túy
Khi 1 ngôn ngữ đem lại 1 cách tiếp cận hoàn toàn mới mẻ với công việc lập trình, khả năng cao là nó đáng để tìm hiểu. Gần 20 năm trước, C++ đáng để tìm hiểu so với C vì C++ chỉ ra thế nào là OOP. 20 năm sau, C++ vẫn đáng để tìm hiểu so với Java vì C++ chỉ ra thể nào là static polymorphism (C++ template) thay vì dynamic polymorphism (Java Generic)
Một ngôn ngữ cũng sẽ trở nên quan trọng nếu nó làm tiền đề cho việc phát minh ra các design patterns mới. Xét theo tiêu chí này, Java cũng rất đáng học vì theo như tôi được biết, có khá nhiều design patterns bắt nguồn từ Java, đơn cử như dependency injection của Martin Fowler.
Gần đây, xu hướng áp dụng các kỹ thuật functional programming (FP) đang tái xuất hiện trong giới lập trình, mà tiêu biểu là sự ra đời của Scala. Scala có thể xem như một sự kết hợp của Java và các kỹ thuật lập trình được ưu chuộng nhất hiện nay, trong đó bao gồm những phát kiến mới của các ngôn ngữ động như Python và Ruby, và rất nhiều ý tưởng lấy từ functional programming.
Tuy nhiên, nhiều người cho rằng Scala không phải là 1 ngôn ngữ quá cồng kềnh và chưa đủ sâu sắc, không khác là mấy so với các web frameworks của Java hiện nay. Functional programming đang là một xu thế và đang trở nên ngày một quan trọng hơn. Mặc dù một tập hợp nhỏ các kỹ thuật FP xuất hiện trong rất nhiều ngôn ngữ – Python, Ruby, Scala, … nhưng sẽ tập trung hơn nếu bạn tiếp cận với FP thông qua 1 ngôn ngữ thuần FP. Và Haskell là 1 ngôn ngữ như thế.
8.2. Haskell được đánh giá là 1 trong những ngôn ngữ có syntax đẹp nhất
Chúng ta định nghĩa hàm sum để tính tổng các phần tử trong 1 list như sau trong Haskell:
sum [] = 0
sum (x:xs) = x + sum xs
Có thể thấy được phần nào độ súc tích và độ sáng sủa của ngôn ngữ này. Và mặc dù Common Lisp hay Scheme đều là FP nhưng syntax của Haskell vẫn được nhièu người đánh giá cao. Đó là 1 hệ thống syntax được design rất sâu sắc, ngắn gọn, dễ hiểu.
8.3. Tính biểu cảm cao
Hai ví dụ về Quicksort trong C và trong Haskell cho ta thấy khả năng biểu diễn rất tốt theo ngôn ngữ tự nhiên của Haskell.
Trong C, đoạn code mô tả Quicksort như sau:
// To sort array a[] of size n: qsort(a,0,n-1) void qsort(int a[], int lo, int hi) {
{ int h, l, p, t; if (lo < hi) { l = lo; h = hi; p = a[hi]; do {
while ((l < h) && (a[l] <= p)) l = l+1;
while ((h > l) && (a[h] >= p)) h = h-1; if (l < h) { t = a[l]; a[l] = a[h]; a[h] = t; } } while (l < h); a[hi] = a[l];
a[l] = p;
qsort( a, lo, l-1 ); qsort( a, l+1, hi ); }
}
Trong khi đó, Quicksort trong Haskell như sau: qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
8.4. Tính sử dụng lại
Một số ngôn ngữ lập trình chỉ thị như Pascal, Ada có kiểu chặt còn Haskell thì ít chặt hơn bởi nó sử dụng khái niệm đa hình. Ví dụ về Quicksort chỉ ra rằng hàm này không chỉ sắp xếp danh sách các số nguyên mà có thể là danh sách các số thực, danh sách các ký tự, danh sách các danh sách, hay thậm chí có thể sắp xếp bất cứ thứ gì tồn tại với khái niệm nhỏ hơn và lớn hơn. Trong khi đó đoạn code C chỉ cho phép sắp xếp mảng các số nguyên. Chính cơ chế đa hình này cho phép sử dụng lại các đoạn mã.
8.5. Định trị trì hoãn
Một đặc điểm rất mạnh của Haskell đó là định trị trì hoãn: chỉ định trị những gì chương trình cần để có được câu trả lời. Điều này cũng tương tự như kỹ thuật pipeline của Unix.
grep printf Foo.c | wc
Đoạn mã Unix trên sẽ đếm số dòng trong file Foo.c có chứa chuỗi printf. Đường ống sẽ lấy kết quả từ lệnh đầu tiên để đưa vào lệnh thứ hai. Hai lệnh này được thực hiện cùng lúc nên không cần thiết phải đưa ra quá nhiều file một lúc.
Nếu lệnh thứ hai chỉ cần một số đầu ra của lệnh thứ nhất thì lệnh thứ nhất không cần thiết phải hoàn thành. Ví dụ:
Những ngôn ngữ không chặt cung cấp tính định trị theo yêu cầu này.
8.6. Khả năng trừu tượng hóa cao
Tính trừu tượng cho phép định nghĩa một đối tượng với những phương thức làm việc bên trong được ẩn dấu. Tính trừu tượng là chìa khóa để xây dựng các module, các chương trình có khả năng bảo trì. Một câu hỏi rất tự nhiên là Haskell cung cấp cơ chế nào để trừu tượng hóa? Câu trả lời là hàm bậc cao: trong Haskell, đối vào của một hàm có thể là một hàm và kết quả trả về cũng vậy