Nhờ đánh giá lười biếng, một chương trình Haskell không (gần như không thể ) làm những gì nó trông giống như nó.
Hãy xem xét chương trình này:
main = putStrLn (show (quicksort [8, 6, 7, 5, 3, 0, 9]))
Trong một ngôn ngữ háo hức, trước tiên quicksort
sẽ chạy, sau đó show
, sau đó putStrLn
. Các đối số của một hàm được tính toán trước khi hàm đó bắt đầu chạy.
Ở Haskell thì ngược lại. Chức năng bắt đầu chạy trước. Các đối số chỉ được tính khi hàm thực sự sử dụng chúng. Và một đối số ghép, giống như một danh sách, được tính toán từng phần một, khi mỗi phần của nó được sử dụng.
Vì vậy, điều đầu tiên xảy ra trong chương trình này là putStrLn
bắt đầu chạy.
Việc triển khai của GHCputStrLn
hoạt động bằng cách sao chép các ký tự của đối số Chuỗi vào bộ đệm đầu ra. Nhưng khi nó vào vòng lặp này, show
vẫn chưa chạy. Do đó, khi nó đi sao chép ký tự đầu tiên từ chuỗi, Haskell sẽ đánh giá phân số của show
và quicksort
các lệnh gọi cần thiết để tính toán ký tự đó . Sau đó putStrLn
chuyển sang ký tự tiếp theo. Vì vậy, việc thực hiện cả ba functions- putStrLn
, show
và quicksort
- được xen kẽ. quicksort
thực thi tăng dần, để lại một biểu đồ của các lần thu hồi không được đánh giá khi nó đi để ghi nhớ nơi nó đã dừng lại.
Bây giờ điều này hoàn toàn khác với những gì bạn có thể mong đợi nếu bạn đã quen thuộc với bất kỳ ngôn ngữ lập trình nào khác. Không dễ để hình dung cách quicksort
thực sự hoạt động trong Haskell về quyền truy cập bộ nhớ hoặc thậm chí thứ tự so sánh. Nếu bạn chỉ có thể quan sát hành vi chứ không phải mã nguồn, bạn sẽ không nhận ra nó đang làm gì như một mạch nhanh .
Ví dụ, phiên bản C của quicksort phân vùng tất cả dữ liệu trước cuộc gọi đệ quy đầu tiên. Trong phiên bản Haskell, phần tử đầu tiên của kết quả sẽ được tính toán (và thậm chí có thể xuất hiện trên màn hình của bạn) trước khi phân vùng đầu tiên chạy xong — thực sự là trước khi bất kỳ công việc nào được thực hiện greater
.
PS Mã Haskell sẽ giống quicksort hơn nếu nó thực hiện cùng số lượng so sánh với quicksort; mã như được viết thực hiện nhiều gấp đôi so sánh vì lesser
và greater
được chỉ định để được tính toán độc lập, thực hiện hai lần quét tuyến tính qua danh sách. Tất nhiên về nguyên tắc, trình biên dịch có thể đủ thông minh để loại bỏ các so sánh thừa; hoặc mã có thể được thay đổi để sử dụng Data.List.partition
.
PPS Ví dụ cổ điển của thuật toán Haskell hóa ra không hoạt động như bạn mong đợi là cái sàng của Eratosthenes để tính toán số nguyên tố.