2013-07-30 25 views
21

Có thể có cơ chế giống như mtl cho máy biến áp đơn nguyên do FreeT/ProgramT tạo ra không?Các lớp thâm nhập Monad Stack với Transformers Monad miễn phí/hoạt động?

Sự hiểu biết của tôi về lịch sử như sau. Đã có một thời gian biến áp monad được phát minh. Sau đó, mọi người bắt đầu để ngăn chặn máy biến áp monad một ngày khác, sau đó tìm thấy nó gây phiền nhiễu để chèn lift ở khắp mọi nơi. Sau đó, một vài người đã phát minh ra các lớp monad, để chúng ta có thể ask :: m r trong bất kỳ đơn vị nào m sao cho MonadReader r m. Đây là có thể bằng cách làm cho mọi tầng lớp đơn nguyên thâm nhập mỗi biến đơn nguyên, như

(Monoid w, MonadState s m) => MonadState s (WriterT w m)
MonadWriter w m => MonadWriter w (StateT s m)

bạn cần cặp như thế tờ khai dụ cho mỗi cặp máy biến áp đơn nguyên, vì vậy khi có n đơn nguyên máy biến áp có n^2 chi phí. Tuy nhiên, đây không phải là một vấn đề lớn, bởi vì mọi người chủ yếu sẽ sử dụng các monads được xác định trước và hiếm khi tạo ra các bản nhạc của riêng họ. Câu chuyện cho đến nay tôi hiểu và cũng được nêu chi tiết, ví dụ: trong sau Q & A:

Avoiding lift with Monad Transformers

Sau đó, vấn đề của tôi là với monads miễn phí mới http://hackage.haskell.org/package/free và monads hoạt động http://hackage.haskell.org/package/operational. Chúng cho phép chúng tôi viết DSL của riêng mình và sử dụng nó làm monads, chỉ bằng cách xác định ngôn ngữ như một số loại đại số data (Hoạt động thậm chí không cần Functor trường hợp). Tin tốt lành là chúng ta có thể có các máy biến đổi đơn nguyên và monad miễn phí; sau đó làm thế nào về các lớp monad? Tin xấu là giả định "chúng ta hiếm khi xác định biến thế đơn nguyên của chúng ta" không còn giữ.

Như một nỗ lực để hiểu vấn đề này, tôi đã thực hiện hai ProgramT và khiến chúng thâm nhập lẫn nhau;

https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs

Gói operational không hỗ trợ lớp đơn nguyên vì vậy tôi đã thực hiện một minioperational và sửa đổi nó để làm việc như tôi cần; https://github.com/nushio3/minioperational

Tuy nhiên, tôi cần khai báo Ví dụ chuyên

instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where

vì tuyên bố chung của các hình thức sau đây dẫn đến trường hợp undecidable.

instance (Monad m, Operational f m) => Operational f (ProgramT g m) where

Câu hỏi của tôi là làm thế nào chúng ta có thể làm cho nó dễ dàng hơn để cho monads hoạt động của chúng tôi thâm nhập lẫn nhau. Hoặc, là mong muốn của tôi để có sự xâm nhập cho bất kỳ đơn vị hoạt động nào bị đặt ra.

Tôi cũng muốn biết các thuật ngữ kỹ thuật chính xác cho thâm nhập :)

Trả lời

6

Tôi đã thử một cách tiếp cận khác nhau chút, mang đến cho ít nhất một câu trả lời một phần.Kể từ khi monads xếp chồng đôi khi có thể là vấn đề, và chúng tôi biết tất cả các monads của chúng tôi được xây dựng từ một số loại dữ liệu, tôi đã cố gắng để kết hợp các loại dữ liệu.

Tôi cảm thấy thoải mái hơn với MonadFree vì vậy tôi đã sử dụng nó, nhưng tôi cho rằng cách tiếp cận tương tự cũng có thể được sử dụng cho Operational.

Hãy bắt đầu với định nghĩa của các kiểu dữ liệu của chúng tôi:

{-# LANGUAGE DeriveFunctor, FlexibleContexts, 
      FlexibleInstances, FunctionalDependencies #-} 
import Control.Monad 
import Control.Monad.Free 

data SLang x = ReadStr (String -> x) | WriteStr String x 
    deriving Functor 
data ILang x = ReadInt (Int -> x) | WriteInt Int x 
    deriving Functor 

Để kết hợp hai functors cùng cho việc sử dụng chúng trong một đơn nguyên miễn phí, hãy định nghĩa coproduct của họ:

data EitherF f g a = LeftF (f a) | RightF (g a) 
    deriving Functor 

Nếu chúng ta tạo một đơn nguyên miễn phí trên EitherF f g, chúng tôi có thể gọi các lệnh từ cả hai. Để thực hiện quá trình này trong suốt, chúng ta có thể sử dụng MPTC để cho phép chuyển đổi từ mỗi functor vào mục tiêu một:

class Lift f g where 
    lift :: f a -> g a 
instance Lift f f where 
    lift = id 

instance Lift f (EitherF f g) where 
    lift = LeftF 
instance Lift g (EitherF f g) where 
    lift = RightF 

bây giờ chúng tôi chỉ có thể gọi lift và chuyển đổi một trong hai phần vào coproduct.

Với một hàm helper

wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a 
wrapLift = wrap . lift . fmap return 

chúng tôi cuối cùng có thể tạo các hàm có thể cho phép chúng ta gọi lệnh từ bất cứ điều gì chúng ta có thể nâng thành một functor:

readStr :: (Lift SLang f, MonadFree f m) => m String 
readStr = wrapLift $ ReadStr id 

writeStr :: (Lift SLang f, MonadFree f m) => String -> m() 
writeStr x = wrapLift $ WriteStr x() 

readInt :: (Lift ILang f, MonadFree f m) => m Int 
readInt = wrapLift $ ReadInt id 

writeInt :: (Lift ILang f, MonadFree f m) => Int -> m() 
writeInt x = wrapLift $ WriteInt x() 

Sau đó, chương trình có thể được biểu diễn như

myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m() 
myProgram = do 
    str <- readStr 
    writeStr "Length of that str is" 
    writeInt $ length str 
    n <- readInt 
    writeStr "you wanna have it n times; here we go:" 
    writeStr $ replicate n 'H' 

mà không xác định thêm bất kỳ trường hợp nào khác.


Trong khi tất cả các công trình trên đều độc đáo, thì vấn đề là làm thế nào để chạy một cách tổng quát các trình tự miễn phí như vậy. Tôi không biết nếu nó thậm chí có thể, để có một giải pháp hoàn toàn chung chung, composable.

Nếu chúng ta chỉ có một functor cơ sở, chúng ta có thể chạy nó như

runSLang :: Free SLang x -> String -> (String, x) 
runSLang = f 
    where 
    f (Pure x)    s = (s, x) 
    f (Free (ReadStr g)) s = f (g s) s 
    f (Free (WriteStr s' x)) _ = f x s' 

Nếu chúng ta có hai, chúng ta cần phải luồn nhà nước của cả hai trong số họ:

runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a) 
runBoth = f 
    where 
    f (Pure x)      s i = ((s, i), x) 
    f (Free (LeftF (ReadStr g)))  s i = f (g s) s i 
    f (Free (LeftF (WriteStr s' x))) _ i = f x s' i 
    f (Free (RightF (ReadInt g)))  s i = f (g i) s i 
    f (Free (RightF (WriteInt i' x))) s _ = f x s i' 

Tôi đoán một khả năng sẽ là để diễn tả các functors bằng cách sử dụng iter :: Functor f => (f a -> a) -> Free f a -> a từ free và sau đó tạo một hàm kết hợp tương tự

iter2 :: (Functor f, Functor g) 
     => (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a 

Nhưng tôi không có thời gian để dùng thử.

+0

Cảm ơn bạn, Petr. Với sự giúp đỡ của bạn, tôi đã hiểu cách kết hợp hai nhà xây dựng kiểu bằng cách sử dụng Hoặc trên '(* -> *)'. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-06.hs Viết thông dịch viên có thể tổng hợp dễ dàng như sau: https://github.com/nushio3/practice/ blob/master/operation/exe-src/test-07.hs Chúng tôi thậm chí có thể soạn nhiều hơn hai ngôn ngữ với chi phí của 'OverlappingInstances'. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-08.hs – nushio