Sự đổi mới rõ ràng nhất được nhận thấy bởi những người mới biết đến Haskell là có một sự tách biệt giữa thế giới không tinh khiết liên quan đến việc giao tiếp với thế giới bên ngoài, và thế giới thuần túy của tính toán và thuật toán. Một câu hỏi dành cho người mới bắt đầu thường xuyên là "Làm thế nào tôi có thể thoát khỏi IO
, tức là chuyển đổi IO a
thành a
?" Cách để nó là sử dụng các đơn nguyên (hoặc trừu tượng khác) để viết mã thực hiện các hiệu ứng IO và chuỗi. Mã này tập hợp dữ liệu từ thế giới bên ngoài, tạo ra một mô hình của nó, thực hiện một số tính toán, có thể bằng cách sử dụng mã thuần và đưa ra kết quả.
Theo như mô hình trên, tôi không thấy có gì sai trái khủng khiếp với việc thao túng GUI trong IO
đơn nguyên. Vấn đề lớn nhất nảy sinh từ phong cách này là các mô-đun không thể kết hợp được nữa, tức là tôi mất hầu hết kiến thức về thứ tự thực hiện toàn cầu của các câu lệnh trong chương trình của mình. Để khôi phục nó, tôi phải áp dụng lý do tương tự như trong mã GUI đồng thời, bắt buộc. Trong khi đó, đối với mã không phải là GUI, thứ tự thực thi là rõ ràng do định nghĩa của toán tử IO
đơn nguyên >==
(ít nhất là miễn là chỉ có một luồng). Đối với mã thuần túy, hoàn toàn không thành vấn đề, ngoại trừ trong các trường hợp góc để tăng hiệu suất hoặc để tránh các đánh giá dẫn đến⊥
.
Sự khác biệt lớn nhất về mặt triết học giữa giao diện điều khiển và IO đồ họa là các chương trình triển khai trước đây thường được viết theo phong cách đồng bộ. Điều này là có thể bởi vì có (để lại các tín hiệu và các bộ mô tả tệp mở khác) chỉ là một nguồn của các sự kiện: luồng byte thường được gọi stdin
. GUI vốn dĩ không đồng bộ và phải phản ứng với các sự kiện bàn phím và nhấp chuột.
Một triết lý phổ biến về thực hiện IO không đồng bộ theo cách chức năng được gọi là Lập trình phản ứng chức năng (FRP). Gần đây, nó đã có rất nhiều lực kéo trong các ngôn ngữ không chức năng, không chức năng nhờ các thư viện như ReactiveX và các khung như Elm. Tóm lại, nó giống như xem các thành phần GUI và những thứ khác (như tệp, đồng hồ, báo thức, bàn phím, chuột) dưới dạng nguồn sự kiện, được gọi là "đài quan sát", phát ra các luồng sự kiện. Những sự kiện này được kết hợp sử dụng các nhà khai thác quen thuộc như map
, foldl
, zip
, filter
, concat
, join
, vv, để sản xuất dòng mới. Điều này rất hữu ích vì chính trạng thái chương trình có thể được xem như là scanl . map reactToEvents $ zipN <eventStreams>
chương trình, trong đóN
bằng với số lượng vật quan sát từng được chương trình xem xét.
Làm việc với các đài quan sát FRP cho phép khôi phục khả năng kết hợp vì các sự kiện trong luồng được sắp xếp kịp thời. Lý do là sự trừu tượng hóa luồng sự kiện cho phép xem tất cả các vật quan sát dưới dạng hộp đen. Cuối cùng, việc kết hợp các luồng sự kiện bằng cách sử dụng các toán tử sẽ trả lại một số thứ tự cục bộ khi thực thi. Điều này buộc tôi phải trung thực hơn nhiều về những bất biến mà chương trình của tôi thực sự dựa vào, tương tự như cách mà tất cả các chức năng trong Haskell phải được minh bạch về mặt tham chiếu: nếu tôi muốn lấy dữ liệu từ một phần khác của chương trình, tôi phải nói rõ quảng cáo khai báo một loại thích hợp cho các chức năng của tôi. (Đơn vị IO, là ngôn ngữ dành riêng cho miền để viết mã không tinh khiết, có hiệu quả phá vỡ điều này)