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 quicksortsẽ 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à putStrLnbắ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, showvẫ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 showvà quicksortcác lệnh gọi cần thiết để tính toán ký tự đó . Sau đó putStrLnchuyển sang ký tự tiếp theo. Vì vậy, việc thực hiện cả ba functions- putStrLn, showvà quicksort- được xen kẽ. quicksortthự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 quicksortthự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ì lesservà 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ố.