2013-04-17 24 views
14

Đây là câu hỏi liên quan đến thực tiễn thiết kế API để xác định các cá thể Monad của riêng bạn cho các thư viện Haskell. Xác định các trường hợp Monad có vẻ là một cách tốt để tách biệt DSL, ví dụ: Par đơn nguyên trong đơn nguyên, hdph; Process trong quá trình phân phối; Eval song song, v.v ...Khi nào (và khi nào không) để xác định Monad

Tôi lấy hai ví dụ về thư viện haskell, mục đích của nó là IO với phần phụ trợ cơ sở dữ liệu. Các ví dụ tôi lấy là riak cho Riak IO và hedis cho Redis IO.

Trong hedis, Redis đơn nguyên is defined. Từ đó, bạn chạy IO với redis như:

data Redis a -- instance Monad Redis 
runRedis :: Connection -> Redis a -> IO a 
class Monad m => MonadRedis m 
class MonadRedis m => RedisCtx m f | m -> f 
set :: RedisCtx m f => ByteString -> ByteString -> m (f Status) 

example = do 
    conn <- connect defaultConnectInfo 
    runRedis conn $ do 
    set "hello" "world" 
    world <- get "hello" 
    liftIO $ print world 

Trong riak, mọi thứ đều khác nhau:

create :: Client -> Int -> NominalDiffTime -> Int -> IO Pool 
ping :: Connection -> IO() 
withConnection :: Pool -> (Connection -> IO a) -> IO a 

example = do 
    conn <- connect defaultClient 
    ping conn 

Các tài liệu cho runRedis nói: "Mỗi cuộc gọi của runRedis mất kết nối mạng từ Connection và chạy hành động Redis đã cho. Lệnh gọi runRedis có thể chặn trong khi tất cả các kết nối từ nhóm đang được sử dụng. ". Tuy nhiên, gói riak cũng thực hiện các pool kết nối. Này được thực hiện mà không trường hợp đơn nguyên thêm trên đầu trang của các đơn nguyên IO:

create :: Client-> Int -> NominalDiffTime -> Int -> IO Pool 
withConnection :: Pool -> (Connection -> IO a) -> IO a 

exampleWithPool = do 
    pool <- create defaultClient 1 0.5 1 
    withConnection pool $ \conn -> ping conn 

Vì vậy, sự tương tự giữa hai gói nắm để hai chức năng:

runRedis  :: Connection -> Redis a -> IO a 
withConnection :: Pool -> (Connection -> IO a) -> IO a 

Theo như tôi có thể nói, gói hedis giới thiệu một đơn nguyên Redis để đóng gói các hành động IO với redis bằng cách sử dụng runRedis. Ngược lại, gói riak trong withConnection chỉ đơn giản là lấy một hàm mất Connection và thực hiện nó trong đơn nguyên IO.

Vì vậy, động lực để xác định các trường hợp Monad của riêng bạn và ngăn xếp Monad là gì? Tại sao gói riak và redis lại khác với cách tiếp cận này?

+6

Như một ngữ cảnh cho câu trả lời - trong trường hợp không rõ ràng, các loại 'Redis a' và' Connection -> IO a' tương đương nhau. Vì vậy, đây thực chất là một sự khác biệt về mỹ phẩm, có thể so sánh với 'env -> IO a' so với' ReaderT env IO a'. –

+0

Điều đó có nghĩa là có lẽ không chính xác và 'Kết nối IO mã hóa 'là đơn nguyên mà anh muốn tất cả cùng. –

Trả lời

10

Đối với tôi, đó là tất cả về đóng gói và bảo vệ người dùng chống lại những thay đổi trong tương lai. Như Casey đã chỉ ra, hai cái này gần như tương đương ngay bây giờ - về cơ bản là một đơn vị Reader Connection. Nhưng hãy tưởng tượng làm thế nào những hành vi này sẽ tùy thuộc vào những thay đổi không chắc chắn xuống đường. Điều gì sẽ xảy ra nếu cả hai gói đều quyết định rằng người dùng cần một giao diện đơn nguyên trạng thái thay vì một trình đọc? Nếu điều đó xảy ra, chức năng withConnection của riak sẽ thay đổi thành chữ ký kiểu như sau:

withConnection :: Pool -> (Connection -> IO (a, Connection)) -> IO a 

Điều này sẽ yêu cầu thay đổi sâu đối với mã người dùng. Nhưng gói Redis có thể rút ra một thay đổi như vậy mà không phá vỡ người dùng của nó.

Bây giờ, người ta có thể cho rằng kịch bản giả định này là rất không thực tế và không phải là thứ bạn cần lập kế hoạch. Và trong hai trường hợp đặc biệt này, điều đó có thể đúng. Nhưng tất cả các dự án phát triển theo thời gian, và thường xuyên theo những cách không lường trước được. Xác định đơn nguyên của riêng bạn cho phép bạn ẩn chi tiết triển khai nội bộ khỏi người dùng của mình và cung cấp giao diện ổn định hơn thông qua các thay đổi trong tương lai.

Khi được nêu theo cách này, một số có thể kết luận rằng việc xác định đơn nguyên của riêng bạn là cách tiếp cận ưu việt. Nhưng tôi không nghĩ đó luôn là trường hợp. (Thư viện lens được coi là một ví dụ có khả năng tốt.) Định nghĩa một đơn nguyên mới có chi phí. Nếu bạn đang sử dụng máy biến áp đơn nguyên, nó có thể áp đặt một hình phạt hiệu suất.Trong các trường hợp khác, API có thể sẽ tiết lộ nhiều hơn. Haskell là rất tốt cho phép bạn giữ cú pháp rất tối thiểu và trong trường hợp đặc biệt này, sự khác biệt không phải là rất lớn - có lẽ một vài liftIO 's cho redis và một vài lambdas cho riak.

Thiết kế phần mềm hiếm khi được cắt và sấy khô. Thật hiếm khi bạn có thể tự tin nói khi nào và khi nào không định nghĩa đơn nguyên của riêng bạn. Nhưng chúng ta có thể trở thành nhận thức được sự cân bằng liên quan để hỗ trợ đánh giá của chúng tôi về các tình huống cá nhân khi chúng ta gặp phải chúng.

1

Trong trường hợp này tôi nghĩ việc triển khai thực hiện đơn nguyên là một sai lầm. Đó là các nhà phát triển java đáng yêu thực hiện tất cả các loại mẫu thiết kế chỉ vì mục đích có chúng. Ví dụ:

hdbc cũng hoạt động trong đơn nguyên IO đơn giản.

Monad cho thư viện redis không mang lại bất kỳ điều gì hữu ích. Điều duy nhất nó đạt được là loại bỏ một đối số hàm (kết nối). Nhưng bạn trả tiền cho nó nâng mỗi hoạt động IO trong khi bên trong redis monad.

Ngoài ra nếu bạn đã bao giờ cần phải làm việc với cơ sở dữ liệu 2 redis bây giờ bạn sẽ có một thời gian khó khăn cố gắng để tìm ra những hoạt động để nâng đâu :)

Lý do duy nhất để thực hiện một đơn nguyên là tạo ra một DSL mới . Như bạn thấy hedis không tạo ra một DSL mới. Các hoạt động của nó giống hệt như bất kỳ thư viện cơ sở dữ liệu nào khác. Vì vậy, đơn nguyên trong hedis là hời hợt và không hợp lý.

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