2015-12-20 25 views
9

Większość monadycznych funkcji przyjmuje czyste argumenty i zwraca monadyczną wartość. Ale istnieje kilka, które również potrzebują monadycznego argumenty, na przykład:Jak kodować akcje, które przyjmują monadyczne argumenty za pomocą darmowych (lub swobodnych) monad?

mplus :: (MonadPlus m) => m a -> m a -> m a 

finally :: IO a -> IO b -> IO a 

forkIO :: m() -> m ThreadId 

-- | From Control.Monad.Parallel 
forkExec :: m a -> m (m a) 

Każdy z nich wydaje się, aby przywołać inny problem i nie mogę uzyskać zrozumienie ogólny sposób jak kodują takie działania za pomocą darmowe monady.

  • Zarówno finally i forkIO Problemem jest to, że monadycznego argument jest innego typu niż wynik. Ale dla za darmo jeden musiałby być tego samego typu, ponieważ IO a zostaje zastąpiony przez zmienną typu typu kodowania, taką jak data MyFunctor x = Finally x x x, która kodowałaby tylko IO a -> IO a -> IO a.

    W From zero to cooperative threads in 33 lines of Haskell code autor używa Fork next next pięść do wdrożenia

    cFork :: (Monad m) => Thread m Bool 
    cFork = liftF (Fork False True) 
    

    a następnie wykorzystuje je do wdrożenia

    fork :: (Monad m) => Thread m a -> Thread m() 
    

    gdzie wejście i wyjście mają różne rodzaje. Ale nie rozumiem, czy zostało to uzyskane przy użyciu jakiegoś procesu lub po prostu pomysłu ad-hoc, który działa w tym konkretnym celu.

  • mplus to szczególnie kłopotliwe: naiwne kodowanie jako

    data F b = MZero | MPlus b b 
    

    rozdziela się >>= i suggested better implementation jest bardziej skomplikowane. A także natywną implementację darmowego MonadPluswas removed from free.

    W freer to realizowane poprzez dodanie

    data NonDetEff a where 
        MZero :: NonDetEff a 
        MPlus :: NonDetEff Bool 
    

    Dlaczego MPlusNonDetEff Bool zamiast NonDetEff a a? I czy istnieje sposób, aby zadziałało to z Free, gdzie potrzebujemy typ danych, aby być funktorem, innym niż używanie CoYoneda functor?

  • Dla forkExec Nie mam pojęcia, jak w ogóle postępować.
+0

Wydaje mi się, że implementacja zależy nie od typu funkcji, ale od jej semantyki. "Ale nie rozumiem, czy zostało to uzyskane przy użyciu jakiegoś procesu" Myślę, że "proces" tutaj po prostu modeluje prawidłowo domenę. Na przykład, myślę, że kodowanie 'finally' będzie bardziej oczywiste, jeśli najpierw spróbujesz kodować" najbardziej ogólne "funkcje wyjątku (' throw', 'catch'?). Typ 'finally' jest taki sam jak' <* '.. więc teoretycznie nie ma powodu, aby funkcja tego typu nie mogła być zakodowana (może po prostu nie bezpośrednio ..). – user2407038

+0

@ user2407038 Rozumiem, co masz na myśli. Ale interesuje mnie podejście "mechanicznie kodują cały interfejs i niech tłumacz będzie martwić się szczegółami implementacji". –

Odpowiedz

3

Będę odpowiadać tylko na temat części monadowej Freer.Przypomnijmy definicję:

data Freer f b where 
    Pure :: b -> Freer f b 
    Roll :: f a -> (a -> Freer f b) -> Freer f b 

teraz z

data NonDetEff a where 
    MZero :: NonDetEff a 
    MPlus :: NonDetEff Bool 

możemy zdefiniować

type NonDetComp = Freer NonDetEff 

Kiedy Roll nakłada się MPlus, a jest zunifikowany z Bool i typu drugiego argumentu jest Bool -> NonDetEff b, która jest w zasadzie krotką:

tuplify :: (Bool -> a) -> (a, a) 
tuplify f = (f True, f False) 

untuplify :: (a, a) -> (Bool -> a) 
untuplify (x, y) True = x 
untuplify (x, y) False = y 

Jako przykład:

ex :: NonDetComp Int 
ex = Roll MPlus $ Pure . untuplify (1, 2) 

Więc możemy zdefiniować instancję MonadPlus dla non-deterministycznych obliczeń

instance MonadPlus NonDetComp where 
    mzero  = Roll MZero Pure 
    a `mplus` b = Roll MPlus $ untuplify (a, b) 

i uruchomić je

run :: NonDetComp a -> [a] 
run (Pure x)  = [x] 
run (Roll MZero f) = [] 
run (Roll MPlus f) = let (a, b) = tuplify f in run a ++ run b