Làm thế nào thường xuyên seq được sử dụng trong mã sản xuất Haskell?


23

Tôi có một số kinh nghiệm khi viết các công cụ nhỏ trong Haskell và tôi thấy nó rất trực quan khi sử dụng, đặc biệt là để viết các bộ lọc (sử dụng interact) xử lý đầu vào tiêu chuẩn của chúng và chuyển nó thành đầu ra tiêu chuẩn.

Gần đây tôi đã thử sử dụng một bộ lọc như vậy trên một tệp lớn hơn khoảng 10 lần so với thông thường và tôi đã Stack space overflowgặp lỗi.

Sau khi đọc một số (ví dụ ở đâyở đây ) tôi đã xác định được hai nguyên tắc để tiết kiệm không gian ngăn xếp (Haskeller có kinh nghiệm, vui lòng sửa cho tôi nếu tôi viết một cái gì đó không đúng):

  1. Tránh các cuộc gọi hàm đệ quy không đệ quy đuôi (điều này hợp lệ đối với tất cả các ngôn ngữ chức năng hỗ trợ tối ưu hóa cuộc gọi đuôi).
  2. Giới thiệu seqđể buộc đánh giá sớm các biểu thức phụ để các biểu thức không tăng quá lớn trước khi chúng bị giảm (điều này đặc trưng cho Haskell, hoặc ít nhất là đối với các ngôn ngữ sử dụng đánh giá lười biếng).

Sau khi giới thiệu năm hoặc sáu seqcuộc gọi trong mã của tôi, công cụ của tôi sẽ chạy lại một cách trơn tru (cũng trên dữ liệu lớn hơn). Tuy nhiên, tôi thấy mã ban đầu dễ đọc hơn một chút.

Vì tôi không phải là một lập trình viên Haskell có kinh nghiệm, tôi muốn hỏi liệu giới thiệu seqtheo cách này có phải là một thông lệ hay không và thông thường người ta sẽ thấy seqtrong mã sản xuất Haskell. Hoặc có bất kỳ kỹ thuật nào cho phép tránh sử dụng seqquá thường xuyên mà vẫn sử dụng ít không gian ngăn xếp?


1
Tối ưu hóa như loại bạn mô tả hầu như luôn luôn sẽ làm cho mã kém thanh lịch hơn một chút.
Robert Harvey

@Robert Harvey: Có bất kỳ kỹ thuật thay thế nào để giữ mức sử dụng ngăn xếp thấp không? Ý tôi là tôi tưởng tượng tôi phải viết lại các chức năng của mình một cách khác nhau nhưng tôi không biết liệu có các kỹ thuật được thiết lập tốt hay không. Nỗ lực đầu tiên của tôi là sử dụng các hàm đệ quy đuôi, điều này giúp nhưng không cho phép tôi giải quyết hoàn toàn vấn đề của mình.
Giorgio

Câu trả lời:


17

Thật không may, có những trường hợp khi người ta phải sử dụng seqđể có được một chương trình hoạt động hiệu quả / tốt cho dữ liệu lớn. Vì vậy, trong nhiều trường hợp, bạn không thể làm gì nếu không có nó trong mã sản xuất. Bạn có thể tìm thêm thông tin trong Real World Haskell, Chương 25. Cấu hình và tối ưu hóa .

Tuy nhiên, có những khả năng làm thế nào để tránh sử dụng seqtrực tiếp. Điều này có thể làm cho mã sạch hơn và mạnh mẽ hơn. Một vài ý tưởng:

  1. Sử dụng ống dẫn , ống hoặc iteratees thay vì interact. Lazy IO được biết là có vấn đề với việc quản lý tài nguyên (không chỉ bộ nhớ) và các vòng lặp được thiết kế chính xác để khắc phục điều này. (Tôi khuyên bạn nên tránh IO lười biếng hoàn toàn cho dù dữ liệu của bạn lớn đến mức nào - xem Vấn đề với I / O lười biếng .)
  2. Thay vì sử dụng seqtrực tiếp (hoặc thiết kế các tổ hợp của riêng bạn) như Foldl ' hoặc Foldr' hoặc các phiên bản nghiêm ngặt của thư viện (như Data.Map.Strict hoặc Control.Monad.State.Strict ) được thiết kế cho các tính toán nghiêm ngặt.
  3. Sử dụng phần mở rộng BangPotypes . Nó cho phép thay thế seqbằng mô hình phù hợp nghiêm ngặt. Khai báo các trường xây dựng nghiêm ngặt cũng có thể hữu ích trong một số trường hợp.
  4. Cũng có thể sử dụng Chiến lược để buộc đánh giá. Thư viện chiến lược chủ yếu nhắm vào các tính toán song song, nhưng cũng có các phương pháp để buộc một giá trị thành WHNF ( rseq) hoặc toàn bộ NF ( rdeepseq). Có nhiều phương pháp tiện ích để làm việc với các bộ sưu tập, kết hợp các chiến lược, v.v.

+1: Cảm ơn những gợi ý và liên kết hữu ích. Điểm 3 có vẻ khá thú vị (và giải pháp dễ nhất để tôi sử dụng ngay bây giờ). Về đề xuất 1, tôi không thấy cách tránh IO lười biếng có thể cải thiện mọi thứ: Theo tôi hiểu thì IO lười biếng nên tốt hơn cho bộ lọc được cho là xử lý luồng dữ liệu (có thể rất dài).
Giorgio

2
@Giorgio Tôi đã thêm một liên kết đến Haskell Wiki về các vấn đề với Lazy IO. Với IO lười biếng, bạn có thể có thời gian quản lý tài nguyên rất khó khăn. Ví dụ: nếu bạn không đọc đầy đủ đầu vào (như do đánh giá lười biếng), xử lý tệp vẫn mở . Và nếu bạn đi và đóng tay cầm tệp một cách thủ công, điều này thường xảy ra do việc đọc đánh giá lười biếng bị hoãn và bạn đóng tay cầm trước khi đọc toàn bộ đầu vào. Và, thường rất khó để tránh các vấn đề về bộ nhớ với IO lười biếng.
Petr Pudlák

Gần đây tôi đã có vấn đề này và chương trình của tôi đã hết phần mô tả tập tin. Vì vậy, tôi thay thế IO lười biếng bằng IO nghiêm ngặt bằng cách sử dụng nghiêm ngặt ByteString.
Giorgio
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.