2017-09-26 17 views
8

Tôi hiện đang xây dựng một API mới, và một trong những chức năng nó hiện đang cung cấp là:Tôi có nên sử dụng MonadUnliftIO hoặc MonadMask cho các chức năng giống như nước lợ không?

inSpan :: Tracer -> Text -> IO a -> IO a 

Tôi đang tìm để di chuyển mà Tracer thành một đơn nguyên, đem lại cho tôi một chữ ký giống như

inSpan :: MonadTracer m => Text -> m a -> m a 

Việc thực hiện inSpan sử dụng bracket, có nghĩa là tôi có hai lựa chọn chính:

class MonadUnliftIO m => MonadTracer m 

hoặc

class MonadMask m => MonadTracer m 

Nhưng tôi nên thích điều gì? Lưu ý rằng tôi kiểm soát tất cả các loại tôi đã đề cập, điều này làm cho tôi hơi nghiêng về phía MonadMask vì nó không thực thi IO ở dưới cùng (có nghĩa là, chúng tôi có thể có trường hợp MonadTracer thuần túy).

Tôi có nên cân nhắc điều gì khác không?

Trả lời

19

Hãy đặt ra các tùy chọn đầu tiên (lặp đi lặp lại một số câu hỏi của bạn trong quá trình này):

  • MonadMask từ thư viện exceptions. Điều này có thể làm việc trên một loạt các monads và máy biến áp, và không yêu cầu đơn nguyên cơ bản là IO.
  • MonadUnliftIO từ thư viện unliftio-core (hoặc unliftio). Thư viện này chỉ hoạt động cho các mono có số IO tại cơ sở của chúng và cách nào đó là đồng nhất với ReaderT env IO.
  • MonadBaseControl từ thư viện monad-control. Thư viện này sẽ yêu cầu IO tại cơ sở, nhưng sẽ cho phép non-ReaderT.

Bây giờ, sự cân bằng. MonadUnliftIO là phần bổ sung mới nhất vào cuộc tranh chấp và có hỗ trợ thư viện ít phát triển nhất. Điều này có nghĩa rằng, ngoài những hạn chế của những gì monads có thể là trường hợp, nhiều trường hợp tốt chưa được viết chưa.

Câu hỏi quan trọng là: tại sao MonadUnliftIO làm cho yêu cầu dường như tùy ý này xung quanh ReaderT giống như mọi thứ? Điều này là để ngăn chặn các vấn đề với trạng thái monadic bị mất. Ví dụ: ngữ nghĩa của bracket_ (put 1) (put 2) (put 3) không thực sự rõ ràng và do đó, MonadUnliftIO không cho phép cá thể StateT.

MonadBaseControl thư giãn giới hạn ReaderT và có hỗ trợ thư viện rộng hơn. Nó cũng được coi là phức tạp hơn nội bộ hơn hai loại kia, nhưng đối với tập quán của bạn thì điều đó không thực sự quan trọng. Và nó cho phép bạn phạm sai lầm với trạng thái đơn điệu như đã đề cập ở trên. Nếu bạn cẩn thận trong việc sử dụng của bạn, điều này sẽ không thành vấn đề.

MonadMask cho phép ngăn xếp biến hoàn toàn thuần túy. Tôi nghĩ rằng có một lý lẽ tốt để có xung quanh tính hữu ích của việc mô hình hóa các ngoại lệ không đồng bộ trong một ngăn xếp thuần túy, nhưng tôi hiểu cách tiếp cận này là điều mà mọi người muốn làm đôi khi.Để đổi lấy nhiều phiên bản hơn, bạn vẫn có các giới hạn xung quanh trạng thái đơn điệu, cộng với không có khả năng nâng một số hành động kiểm soát IO, như timeout hoặc forkIO.

Tôi đề nghị:

  • Nếu bạn muốn để phù hợp với cách mà hầu hết mọi người đang làm việc ngày hôm nay, nó có thể là tốt nhất để chọn MonadMask, nó là giải pháp tốt thông qua nhất.
  • Nếu bạn muốn mục tiêu đó, nhưng bạn cũng cần phải thực hiện timeout hoặc withMVar hoặc một cái gì đó, sử dụng MonadBaseControl.
  • Và nếu bạn biết có một bộ monads cụ thể mà bạn cần tương thích và muốn biên dịch thời gian đảm bảo về tính chính xác của mã trạng thái đơn thuần của bạn, hãy sử dụng MonadUnliftIO.
Các vấn đề liên quan