Klasy penetracji stosu Monad z darmowymi / operacyjnymi transformatorami Monad?
Czy istnieje mechanizm podobny do mtl dla transformatorów monad stworzonych przez FreeT / ProgramT ?
[12]}moje rozumienie historii jest następujące. Dawno, dawno temu wynaleziono transformator monad. Potem ludzie zaczęli układać transformatory monad jeden na drugim, a potem okazało się, że irytujące jest wstawianielift
wszędzie. Wtedy kilka osób wymyśliło klasy monad, abyśmy mogli np. ask :: m r
W dowolnej monadzie m
takiej, że MonadReader r m
. Było to możliwe dzięki temu, że każda klasa monad przeniknęła każdy transformator monad, jak
(Monoid w, MonadState s m) => MonadState s (WriterT w m)
MonadWriter w m => MonadWriter w (StateT s m)
Potrzebujesz takiej pary deklaracji instancji dla każdej pary transformatorów monad, więc gdy jest N transformatory monad, to jest N ^2. Nie był to jednak duży problem, ponieważ ludzie najczęściej używają predefiniowanych monad i rzadko tworzą własne. Historia do tej pory Rozumiem, a także jest szczegółowy np. w następującym Q & A:
Unikanie podnoszenia z Monad Transformers
Więc mój problem jest z nowymi darmowymi monadami http://hackage.haskell.org/package/free i monady operacyjne http://hackage.haskell.org/package/operational . Pozwalają nam pisać własne DSL i używać go jako monad, po prostu definiując język jako jakiś algebraiczny typ data
(operacyjny nie potrzebuje nawet instancji Functor
). Dobrą wiadomością jest to, że możemy mieć monady i transformatory monad za darmo; to co powiesz na klasy monad? Zła wiadomość jest taka, że założenie "rzadko definiujemy własne transformatory monad" już nie istnieje.
ProgramT
i sprawiłem, że przenikają się nawzajem;
Https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs
Pakiet operational
nie obsługuje klas monad, więc wziąłem kolejną implementację minioperational
i zmodyfikowałem ją tak, aby działała jak trzeba; https://github.com/nushio3/minioperational
Still, I needed the specialized instance declaration
instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where
Ponieważ ogólna deklaracja poniższej formy prowadzi do nierozstrzygalnych przypadków.
[12] moje pytanie brzmi, jak możemy ułatwić naszym operacyjnym monadom przenikanie się nawzajem. Lub, jest moim życzeniem, aby mieć penetrację dla każdego operacyjnego monad źle pozowane.
instance (Monad m, Operational f m) => Operational f (ProgramT g m) where
Chciałbym również znać poprawny termin techniczny dla penetracji :)
1 answers
Próbowałem nieco innego podejścia, które daje przynajmniej częściową odpowiedź. Ponieważ układanie monad może być czasami problematyczne, a wiemy, że wszystkie nasze monady są zbudowane z jakiegoś typu danych, próbowałem zamiast tego połączyć typy danych.
Czuję się bardziej komfortowo z MonadFree
więc go użyłem, ale przypuszczam, że podobne podejście można również zastosować do Operational
.
Zacznijmy od definicji naszych typów danych:
{-# 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
Aby połączyć dwa funktory razem dla używając ich w wolnej monadzie, zdefiniujmy ich koprodukt:
data EitherF f g a = LeftF (f a) | RightF (g a)
deriving Functor
Jeśli stworzymy darmową monadę nad EitherF f g
, możemy wywołać polecenia z obu z nich. Aby uczynić ten proces transparentnym, możemy użyć MPTC , aby umożliwić konwersję z każdego functora na docelowy:
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
Teraz możemy po prostu wywołać lift
i przekonwertować obie części do koproduktu.
Z funkcją pomocniczą
wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a
wrapLift = wrap . lift . fmap return
Możemy wreszcie stworzyć ogólne funkcje, które pozwalają do wywołania komend z każdego, co możemy podnieść do functora:
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 ()
Wtedy program może być wyrażony jako
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'
Bez definiowania dalszych instancji.
Podczas gdy wszystkie powyższe działa ładnie, problem polega na tym, jak ogólnie uruchomić tak skomponowane darmowe monady. Nie wiem, czy jest w ogóle możliwe, aby mieć w pełni generyczne, kompozytowe rozwiązanie.
Jeśli mamy tylko jeden funktor bazowy, możemy go uruchomić jako
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'
Jeśli mamy dwa, musimy wątek stanu obu z nich:
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'
Domyślam się, że jedną z możliwości byłoby wyrażenie uruchamiania funktorów za pomocą iter :: Functor f => (f a -> a) -> Free f a -> a
z free, a następnie utworzenie podobnej, łączącej funkcji
iter2 :: (Functor f, Functor g)
=> (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a
Ale nie miałem czasu, aby go wypróbować.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-02 11:22:10