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 wstawianie lift 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.

Aby zrozumieć ten problem, zrobiłem dwa 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.

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

[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.

Chciałbym również znać poprawny termin techniczny dla penetracji :)

Author: Community, 2013-07-30

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ć.

 6
Author: Petr Pudlák,
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