2010-03-05 25 views
15

Tôi đang suy nghĩ về cách sử dụng hệ thống kiểu của Haskell để thực thi mô đun trong một chương trình. Ví dụ, nếu tôi có một ứng dụng web, tôi tò mò nếu có một cách để tách tất cả các mã cơ sở dữ liệu khỏi mã CGI từ mã hệ thống tập tin từ mã thuần túy.Sử dụng hệ thống kiểu Haskell để thực thi mô đun

Ví dụ, tôi hình dung một đơn nguyên DB, vì vậy tôi có thể viết các chức năng như:

countOfUsers :: DB Int 
countOfUsers = select "count(*) from users" 

Tôi muốn nó là không thể sử dụng các tác dụng phụ khác so với những người được hỗ trợ bởi các đơn nguyên DB. Tôi đang hình dung một đơn vị cấp cao hơn sẽ bị giới hạn đối với các trình xử lý URL trực tiếp và sẽ có thể soạn các cuộc gọi đến trình đơn DB và trình đơn IO.

Điều này có khả thi không? Đây có phải là khôn ngoan không?

Cập nhật: tôi đã kết thúc đạt được điều này với Scala thay vì Haskell: http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html

Trả lời

13

tôi hình dung một đơn nguyên cấp cao hơn mà sẽ bị giới hạn đối với trình xử lý URL trực tiếp và sẽ có thể soạn các cuộc gọi đến đơn nguyên DB và đơn nguyên IO.

Bạn chắc chắn có thể đạt được điều này và nhận được các đảm bảo tĩnh rất mạnh về việc tách các thành phần.

Tại đơn giản nhất, bạn muốn một đơn vị IO bị giới hạn. Sử dụng một cái gì đó giống như một kỹ thuật "tainting", bạn có thể tạo một tập hợp các hoạt động IO được nâng lên thành một trình bao bọc đơn giản, sau đó sử dụng hệ thống mô-đun để ẩn các hàm tạo bên dưới cho các kiểu.

Bằng cách này, bạn sẽ chỉ có thể chạy mã CGI trong ngữ cảnh CGI và mã DB trong ngữ cảnh DB. Có rất nhiều ví dụ về Hackage.

Một cách khác là xây dựng một thông dịch viên cho các hành động, và sau đó sử dụng các nhà xây dựng dữ liệu để mô tả từng hoạt động ban đầu mà bạn muốn. Các hoạt động vẫn nên tạo một đơn nguyên và bạn có thể sử dụng ký hiệu, nhưng thay vào đó bạn sẽ xây dựng cấu trúc dữ liệu mô tả các hành động để chạy, sau đó bạn thực thi theo cách được điều khiển thông qua trình thông dịch.

Điều này mang lại cho bạn nhiều sự quan tâm hơn bạn cần trong các trường hợp điển hình, nhưng cách tiếp cận này cung cấp cho bạn toàn bộ sức mạnh để mã người dùng không mong muốn trước khi bạn thực thi nó.

+0

Cảm ơn, Don! Các giải pháp trước đây âm thanh như những gì tôi đang tìm kiếm. Bạn có biết bất kỳ gói cụ thể nào sử dụng kỹ thuật này hay các thuật ngữ tốt cho google cho ("hạn chế IO monad" không bật lên nhiều)? – Bill

+1

Một ví dụ điển hình về khái niệm 'đơn sơ yếu', http://blog.sigfpe.com/2007/04/trivial-monad.html –

+0

Cảm ơn. Nếu tôi chọn sử dụng mẫu "monome độc ​​hại" cho đơn nguyên DB của mình, tôi phải làm gì để trích xuất dữ liệu từ đơn nguyên DB? Có xử lý hành động HTTP của tôi phải sử dụng một biến áp monad với DB trong nó? – Bill

4

Cảm ơn cho câu hỏi này!

Tôi đã thực hiện một số công việc trên một khung công tác web/máy khách sử dụng monads để phân biệt giữa các môi trường exection khác nhau. Những rõ ràng là client-sideserver-side, nhưng nó cũng cho phép bạn viết cả-side đang (có thể chạy trên cả client và server, bởi vì nó không chứa bất kỳ tính năng đặc biệt) và cũng phía máy khách không đồng bộ được sử dụng để viết mã không chặn trên máy khách (về bản chất là một đơn nguyên tiếp tục ở phía máy khách). Điều này nghe có vẻ khá liên quan đến ý tưởng phân biệt giữa mã CGI và mã DB.

Dưới đây là một số tài nguyên về dự án của tôi:

  • Slides từ một bài thuyết trình mà tôi đã về dự án
  • Draft paper mà tôi đã viết với Don Syme
  • Và tôi cũng viết của tôi Bachelor thesis về chủ đề này (mặc dù khá dài)

Tôi nghĩ đây là một cách tiếp cận thú vị và nó có thể mang đến cho bạn sự bảo đảm thú vị về mã. Có một số câu hỏi khó khăn mặc dù. Nếu bạn có chức năng phía máy chủ cần có một số int và trả về int, thì loại chức năng này là gì? Trong dự án của tôi, tôi đã sử dụng int -> int server (nhưng cũng có thể sử dụng server (int -> int).

Nếu bạn có một vài chức năng như thế này thì không phải đơn giản để soạn chúng. Thay vì viết goo (foo (bar 1)), bạn cần viết đoạn mã sau:.

do b <- bar 1 
    f <- foo b 
    return goo f 

Bạn có thể viết những điều tương tự sử dụng một số combinators, nhưng quan điểm của tôi là thành phần là một chút ít thanh lịch

+0

'import Control.Monad; (goo <= ephemient

+0

Vâng, điều này còn tồi tệ hơn trong F #, nơi bạn cũng muốn viết các lời gọi phương thức như 'o.Bar(). Foo(). Goo()' và không có cách nào để thực hiện điều này bằng cách sử dụng bộ kết hợp. Sử dụng '<= <' trong Haskell có vẻ OK, nhưng vẫn không hoàn hảo. –

5

Tôi nghĩ rằng có một cách thứ ba ngoài hai Don Stewart đã đề cập, mà thậm chí có thể đơn giản hơn:

class Monad m => MonadDB m where 
    someDBop1 :: String -> m() 
    someDBop2 :: String -> m [String] 

class Monad m => MonadCGI m where 
    someCGIop1 :: ... 
    someCGIop2 :: ... 

functionWithOnlyDBEffects :: MonadDB m => Foo -> Bar -> m() 
functionWithOnlyDBEffects = ... 

functionWithDBandCGIEffects :: (MonadDB m, MonadCGI m) => Baz -> Quux -> m() 
functionWithDBandCGIEffects = ... 

instance MonadDB IO where 
    someDBop1 = ... 
    someDBop2 = ... 

instance MonadCGI IO where 
    someCGIop1 = ... 
    someCGIop2 = ... 

Ý tưởng rất đơn giản mà bạn xác định các lớp học kiểu cho các tập con khác nhau của hoạt động bạn muốn tách riêng ra, và sau đó parametrize chức năng của bạn bằng cách sử dụng chúng. Ngay cả khi monad cụ thể duy nhất bạn tạo một thể hiện của các lớp là IO, các hàm được parametrized trên bất kỳ MonadDB nào sẽ vẫn chỉ được phép sử dụng các phép toán MonadDB (và những cái được xây dựng từ chúng), vì vậy bạn đạt được kết quả mong muốn. Và trong một hàm "có thể làm bất cứ điều gì" trong đơn nguyên IO, bạn có thể sử dụng các hoạt động MonadDB và MonadCGI liền mạch, bởi vì IO là một cá thể.

(Tất nhiên, bạn có thể xác định các trường hợp khác nếu bạn muốn. Tôi sẽ thực sự không ngăn bạn viết trường hợp cho trình bao bọc "" và "thông dịch" monads Don Stewart đề cập, do đó kết hợp các phương pháp tiếp cận - mặc dù tôi không chắc chắn nếu có một lý do bạn sẽ muốn.)

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