2013-08-28 11 views
6

Dlaczego nie MonadTrans jest zdefiniowany jakoDlaczego wartość powrotu windy nie jest monadą?

class MonadTrans t where 
    lift :: (Monad m, Monad (t m)) => m a -> t m a 
--     ^^^^^^^^^^^ 

zamiast obecnego

class MonadTrans t where 
    lift :: Monad m => m a -> t m a 

To Haskell 98 (w przeciwieństwie do sugestii w Why aren't monad transformers constrained to yield monads?) i zapewnia, że ​​wynik będzie zawsze monada. Czy istnieje powód, dla którego transformatory monady mogą wytwarzać coś, co nie jest monadą?

+1

Dlaczego chcesz, aby zapewnić wynik jest monada? Byłoby to zdecydowanie mniej ogólne i nie widzę z tego żadnej korzyści. –

+3

@JohnL Przede wszystkim dlatego, że ['MonadTrans' praw] (http://hackage.haskell.org/packages/archive/transformers/0.3.0.0/doc/html/Control-Monad-Trans-Class.html#t:MonadTrans) wyrażone są monadycznie, więc wynik musi być monadą. Jeśli tak nie jest, praw nie można nawet wyrazić. Ale bheklilr dał dobry punkt w swojej odpowiedzi i na tej podstawie zrobiłem [przykład] (http://stackoverflow.com/a/18495255/1333025), który produkuje transformator 'Monad' ->' Applicative'. Oznacza to jednak, że musimy sformułować inny zestaw praw. –

Odpowiedz

4

Domyślam się, że MonadTrans przekształca Monad w coś innego, zamiast przekształcania Monad w Monad. Jest bardziej ogólny, ponieważ możesz napisać coś, co przekształca Monad i możesz zdefiniować lift, ale nie możesz zdefiniować >>= i return. Ponieważ większość (jeśli nie wszystkie) instancji MonadTrans kończy się na Monad s, tak naprawdę nie stanowi problemu, ponieważ kompilator nadal go obsługuje.

+0

Brak osobistej wiedzy, ale spodziewam się, że to jest powód. Wydaje się prawdopodobne, że możesz potrzebować instancji 'MonadTrans' dla' Functor' lub 'Applicative', która nie jest' Monad'. –

+0

@JohnL To było dokładnie moje rozumowanie. Nie powołuję się na żadne źródła, ponieważ nie znam żadnych źródeł (więc jeśli ktokolwiek ma taką dokumentację, to możesz to opublikować), ale sensownym byłoby, że MonadTrans działa tylko na jednej monadzie zamiast dwóch. – bheklilr

6

Odpowiedź bheklilra dała mi przykład, w którym transformator monady wytwarza coś, co nie jest monadą. Dobrze znanym przykładem czegoś, co nie jest monadą, jest ZipList. I możemy stworzyć wariant, który uruchamia monadyczne działanie na każdym poziomie:

import Control.Applicative 
import Control.Arrow ((***)) 
import Control.Monad 
import Control.Monad.Trans 

-- | A list where each step is produced by a monadic action. 
data ListT m a = Nil | Cons (m (a, ListT m a)) 

To jest właściwie monadowy strumień. I może być łatwo przekształcony w Functor i Applicative

instance Monad m => Functor (ListT m) where 
    fmap f Nil  = Nil 
    fmap f (Cons k) = Cons $ (f *** fmap f) `liftM` k 
instance Monad m => Applicative (ListT m) where 
    pure x = Cons $ return (x, pure x) 
    Cons mf <*> Cons mx = Cons $ do 
     (f, fs) <- mf 
     (x, xs) <- mx 
     return (f x, fs <*> xs) 
    _  <*> _  = Nil 

ale oczywiście nie jest to monada. Mamy więc instancję MonadTrans, która konwertuje monadę w coś, co jest tylko Applicative.

instance MonadTrans ListT where 
    lift mx = Cons $ (\x -> (x, lift mx)) `liftM` mx 

(To wszystko uświadomiło mi, że eksperymentalny ZipSink w przewód-extra jest również ładny takich przykładów.)


Jednak to rodzi kolejne pytanie: jeśli chcemy takich transformatory, jakie prawa powinny przestrzegać? Przepisy prawne dotyczące MonadTrans są zdefiniowane jako

lift . return = return 
lift (m >>= f) = lift m >>= (lift . f) 

Tak więc w naszym przypadku moglibyśmy chcieć czegoś jak

lift (f `liftM` x) = fmap f (lift x) 

lift . return  = pure 
lift (m `ap` f)  = lift m <*> lift f 
+0

Zauważ, że jeśli zawiniesz 'ListT' w jedną ostatnią warstwę monady, to jest to monada, konkretnie [" ListT done right "] (http://www.haskell.org/haskellwiki/ListT_done_right). –

+0

@GabrielGonzalez Tak, pamiętanie "ListT" było moją inspiracją. Ale mój 'ListT' definiuje' czysty' i '<*>' inaczej, tak jak ['ZipList'] (http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Applicative.html #t: ZipList) różni się od '[]'. Więc nie ma ważnej instancji monady. –

3

mam zamiar zgadzam się z dwoma pozostałymi odpowiedziami powiedzieć, że wynikiem powinna być monada. Powodem jest to, że w przeciwnym razie nie istnieją rozsądne prawa, które powinny być przestrzegane.

lift ma być morfizmami Monad, co oznacza, że ​​powinien przestrzegać następujących dwóch praw:

lift (return x) = return x 

lift (m >>= f) = lift m >>= \r -> lift (f r) 

Przepisy te bardziej sensowne, kiedy zdajesz sobie sprawę, że są przepisy funktor między dwie kategorie Kleisli:

-- i.e. "fmap id = id" 
(lift .) return = return 

-- i.e. "fmap (f . g) = fmap f . fmap g" 
(lift .) (f >=> g) = (lift .) f >=> (lift .) g 

Jeśli jednak nie ograniczysz wyjścia do monady, prawa te przestaną być poprawne i nie będziesz miał rozsądnego sposobu na sprawdzenie, czy poprawnie zaimplementowano lift.

Podejrzewam, że prawdziwym powodem było uczynienie klasa Haskell98

+0

Mój pomysł na 'lift :: (Monad m, Monad (t m)) => m a -> t m a' zdaje się być H98. Sugerujesz więc, że dla takich rzeczy jak 'ZipListT' powinniśmy raczej użyć innego transformatora aplikacyjnego z czymś podobnym do' lift :: (Applicative f) => f a -> t f a'? –

+0

@ PetrPudlák Nie znam prawdziwej przyczyny braku przymusu, więc po prostu zgaduję. Jakie prawa zastosowałby transformator aplikacyjny? –

+0

Wyobrażam sobie, że transformator aplikacyjny powinien być zgodny z prawem aplikacyjnym, a transformator funktorowy musi być zgodny z prawami funktora. Chociaż najprawdopodobniej byłby to spiczasty funktor, więc 'lift. pure = pure' powinien nadal być ważny. Myślę, że wystarczyłoby do pracy. –

Powiązane problemy