2013-03-21 23 views
15

xem xét sau Ví dụ:Có sử dụng mapM/sequence được coi là thực hành tốt không?

safeMapM f xs = safeMapM' xs [] 
    where safeMapM' []  acc = return $ reverse acc 
      safeMapM' (x:xs) acc = do y <- f x 
            safeMapM' xs (y:acc) 

mapM return largelist  -- Causes stack space overflow on large lists 
safeMapM return largelist -- Seems to work fine 

Sử dụng mapM trên các danh sách lớn gây ra một tràn không gian ngăn xếp trong khi safeMapM dường như làm việc tốt (sử dụng GHC 7.6.1 với -O2). Tuy nhiên tôi đã không thể tìm thấy một chức năng tương tự như safeMapM trong các thư viện chuẩn Haskell.

Vẫn được coi là thực tiễn tốt để sử dụng mapM (hoặc sequence cho vấn đề đó)?
Nếu vậy, tại sao nó được coi là thực hành tốt mặc dù sự nguy hiểm của tràn không gian ngăn xếp?
Nếu bạn không đề xuất phương án thay thế nào?

+0

Có lẽ 'mapM' nhanh hơn nếu nó không tràn vì bạn không phải' đảo ngược'? Bạn đã đo nó? –

+0

bạn có thể đăng mô-đun 'Chính' mà bạn đã sử dụng để kiểm tra không? – jberryman

+4

Ngoài ra, có những monads (như 'Control.Monad.State.Lazy'), nơi có một cái gì đó giống như' lấy 100 <$> mapM id [1 ..] 'chấm dứt. 'lấy 100 <$> safeMapM id [1 ..]' không thể chấm dứt, bất kể monad –

Trả lời

9

Như Niklas B., ngữ nghĩa của mapM là của một lần có hiệu lực ngay, và nó chấm dứt thành công trong nhiều trường hợp hơn là một phiên bản lật. Nói chung, mapM có ý nghĩa hơn, vì hiếm khi chúng tôi muốn tạo bản đồ kết quả trên một danh sách dữ liệu khổng lồ. Thông thường hơn, chúng tôi sẽ muốn đánh giá danh sách này cho các hiệu ứng và trong trường hợp đó mapM_sequence_, điều này sẽ loại bỏ các kết quả, thường là những gì được khuyến nghị.

Chỉnh sửa: nói cách khác, mặc dù vấn đề được nêu ra trong câu hỏi, , mapMsequence thường được sử dụng và thường được coi là thực hành tốt.

+1

Tôi muốn mã của mình chạy trong mọi trường hợp, không chỉ trong "nhiều trường hợp" hơn. Và rất nhiều hiệu ứng đi kèm với các giá trị trả về, ví dụ: đầu vào của người dùng. Ví dụ Niklas B. Tôi có thể nắm bắt khi phát triển, ngăn xếp tràn không gian với các danh sách lớn có thể xảy ra sau khi triển khai. Rất khó tìm thấy imho – jonnydee

+2

có ngữ nghĩa khác nhau và sự cân bằng khác nhau. vì vậy bạn cần phải chọn một trong đó phù hợp với tình hình của bạn. đó là câu trả lời! câu trả lời duy nhất! mã khác mà bạn cung cấp cũng sẽ không thành công trên các danh sách đủ lớn - nó sẽ chỉ lấy các danh sách lớn hơn. bạn cũng có thể thêm không gian ngăn xếp vào chương trình. bạn biết đấy, hiểu được sự cân bằng và sau đó lựa chọn. – sclv

6

Nếu vậy, tại sao nó được coi là thực hành tốt mặc dù nguy cơ tràn không gian ngăn xếp? Nếu bạn không đề nghị sử dụng giải pháp thay thế nào?

Nếu bạn muốn xử lý các phần tử danh sách khi chúng được tạo, hãy sử dụng pipes hoặc conduit. Cả hai sẽ không bao giờ xây dựng một danh sách trung gian.

Tôi sẽ hiển thị theo cách pipes, vì đó là thư viện của tôi. đầu tiên tôi sẽ bắt đầu với một danh sách vô hạn các số được tạo ra trong IO đơn nguyên từ người dùng nhập vào:

import Control.Proxy 

infiniteInts :: (Proxy p) =>() -> Producer p Int IO r 
infiniteInts() = runIdentityP $ forever $ do 
    n <- lift readLn 
    respond n 

Bây giờ, tôi muốn in chúng khi chúng được tạo ra. Điều đó đòi hỏi việc xác định một handler hạ lưu:

printer :: (Proxy p) =>() -> Consumer p Int IO r 
printer() = runIdentityP $ forever $ do 
    n <- request() 
    lift $ print n 

Bây giờ tôi có thể kết nối ProducerConsumer sử dụng (>->), và chạy các kết quả sử dụng runProxy:

>>> runProxy $ infiniteInts >-> printer 
4<Enter> 
4 
7<Enter> 
7 
... 

rằng sau đó sẽ đọc Int s từ người sử dụng và echo chúng trở lại giao diện điều khiển khi chúng được tạo ra mà không lưu nhiều hơn một phần tử duy nhất trong bộ nhớ.

Vì vậy, thông thường nếu bạn muốn tính toán hiệu quả tạo luồng luồng các yếu tố và tiêu thụ chúng ngay lập tức, bạn không muốn mapM. Sử dụng thư viện phát trực tuyến thích hợp.

Nếu bạn muốn tìm hiểu thêm về pipes, thì tôi khuyên bạn nên reading the tutorial.

+0

Câu trả lời này hơi lỗi thời. Bạn có thể muốn cập nhật nó. – dfeuer

-1

Nếu bạn muốn ở trong trại lười biếng, gói lazyio cho phép bạn xử lý danh sách đầu vào một cách lười biếng. Thay vì

mapM f 

bạn viết

import qualified System.IO.Lazy as LazyIO 

LazyIO.run . mapM (LazyIO.interleave . f) 

Không stack overflow nữa.

Các vấn đề liên quan