2013-11-23 21 views
7

W dokumentacji Haskella Control.Arrow mówi o związku strzały Kleisli z monadami, ale nie jest dla mnie oczywiste, jak z tego korzystać. Mam funkcję, która, jak sądzę, pasuje do strzał, z wyjątkiem tego, że zawiera monotonię IO, więc myślę, że strzałki Kleisli mogą pomóc.Jak używać strzałek Kleisli z monadami?

Wykonaj następującą funkcję, która zwraca pary oryginalnych i zmodyfikowanych nazw plików katalogu.

import System.Directory 
import System.FilePath 

datedFiles target = do 
    fns <- getDirectoryContents target 
    tms <- mapM (fmap show . getModificationTime) fns 
    return $ 
     zip fns $ 
     zipWith replaceBaseName fns $ 
     zipWith (++) (map takeBaseName fns) tms 

Gdybym miał wyciągnąć go na zewnątrz, to byłoby coś takiego:

enter image description here

Myślę, że może korzystać z wykorzystaniem strzałek Kleisli, ale nie wiem jak . Czy ktoś może udzielić wskazówek?

Odpowiedz

6

datedFiles mogą być realizowane za pomocą strzałek, ponieważ przepływ informacji w „stałym rurociągiem”, jak swoimi pokazuje diagram.

Oto możliwe wdrożenie że nie używa map lub zip na listach:

import System.Directory 
import System.FilePath 
import Control.Monad.List 
import Control.Arrow 

datedFiles :: FilePath -> IO [(FilePath,FilePath)] 
datedFiles = fmap runListT . runKleisli $ 
    (Kleisli $ ListT . getDirectoryContents) 
    >>> 
    returnA &&& ((Kleisli $ liftIO . getModificationTime) >>^ show) 
    >>^ 
    fst &&& (\(path,time) -> replaceBaseName path $ takeBaseName path ++ time) 

Prawdopodobnie nie jest to najbardziej intuicyjny realizacja.

Monada na strzałki Kleisli to ListT IO, chociaż jedynym niedeterministwem jest getDirectoryContents.

Zauważ, że ostatnia linia jest czystą funkcją; (&&&) dla ostatniej linii wykorzystuje instancję Arrow dla funkcji.

Edit: W Wrapped typeclass z pakietu lens może być używany do dodawania/usuwania owijarki newtype nieco bardziej zwięźle. Stosując go do poprzedniego przykładu, otrzymujemy:

import Control.Lens 

datedFiles :: FilePath -> IO [(FilePath,FilePath)] 
datedFiles = fmap runListT . runKleisli $ 
    ListT . getDirectoryContents ^. wrapped 
    >>> 
    returnA &&& (liftIO . getModificationTime ^. wrapped >>^ show) 
    >>^ 
    fst &&& (\(path,time) -> replaceBaseName path $ takeBaseName path ++ time) 
7

Monady są Functor s od Hask, kategorii typów i funkcji Haskell, do Hask --- endofunctor. Oznacza to, że niektóre ze strzał w Hask wygląda jak a -> m b dla niektórych Monadm. Dla konkretnej monady m, podkategoria Hask, gdzie strzałki wyglądają jak a -> m b, jest kategorią Kleisli dla m.

Wiemy, że jest to kategoria, ponieważ istnieje tożsamość strzałka return :: a -> m a i skład (>>>) :: (a -> m b) -> (b -> m c) -> (a -> m c) zdefiniowane jak

(f >>> g) a = join (g <$> f a) 

dlatego też musimy, że jest to Monad --- używamy zarówno return i join.


W Haskell, nie możemy mieć podkategorię normalnie, ale zamiast newtype służy.

import Prelude hiding ((.), id) 
import Control.Category 

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b } 

instance Monad m => Category (Kleisli m) where 
    id     = Kleisli return 
    Kleisli g . Kleisli f = Kleisli (join . fmap g . f) 

A potem możemy uaktualnić funkcje typu Monad m => a -> m b do Kleisli m a b s, strzałki w kategorii i komponować je (.)

arr :: Kleisli IO FilePath [String] 
arr = Kleisli (mapM $ fmap show . getModificationTime) . Kleisli getDirectoryContents 

Ogólnie to trochę składniowo głośny, choć. Ten nowy typ jest cenny tylko dlatego, aby użyć typeklasy Category do przeciążenia id i (.). Zamiast tego jest to bardziej prawdopodobne, że zobaczysz return i (>=>) które są równoważne

return a = runKleisli (id a) 
f >=> g = runKleisli $ Kleisli g . Kleisli f 
2

Najpierw proponuję podzielenie obsługi pojedynczego pliku z obsługi listy. W twoim przykładzie interesująca strzałka to timestamp, ponieważ wszystkie pozostałe są czystymi funkcjami. Niemniej jednak niektóre z nich można wykorzystać w strzałki, aby uczynić ten przykład bardziej interesującym. Korzystanie z arrow notation możemy przepisać obliczeniowej jedną nazwę pliku jako Kleisli strzałka:

{-# LANGUAGE Arrows #-} 
import Control.Arrow 
import System.Directory 
import System.FilePath 
import System.Time 

-- Get a timestamp of a file as an arrow: 
timestamp :: Kleisli IO FilePath ClockTime 
timestamp = Kleisli getModificationTime 

-- Insert a given string in front of the extension of a file. 
-- Just as an example - we'd rather use a simple `let` instead of making it 
-- an arrow. 
append :: (Monad m) => Kleisli m (FilePath, String) FilePath 
append = arr $ \(fn, suffix) -> 
       let (base, ext) = splitExtension fn 
       in base ++ suffix ++ ext 

-- Given a directory, receive the name of a file as an arrow input 
-- and produce the new file name. (We could also receive `dir` 
-- as an input, if we wanted.) 
datedArrow :: FilePath -> Kleisli IO FilePath (FilePath, FilePath) 
datedArrow dir = proc fn -> do 
        ts <- timestamp -< replaceDirectory fn dir 
        fn' <- append -< (fn, show ts) 
        returnA -< (fn, fn') 

datedFiles' :: FilePath -> IO [(FilePath, FilePath)] 
datedFiles' target = do 
       fns <- getDirectoryContents target 
       mapM (runKleisli $ datedArrow target) fns 
1

Niech pamiętają funkcję głównego z Monady:

(>>=) :: (a -> m b) -> m a -> m b 

a teraz przyjrzyjmy się Kleisli

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b } 

gdzie Kleisli to opakowanie i runKleisli - rozwijanie z nowego typu.

Co jest wspólnego? a -> m b część

I spójrzmy na deklaracji instancji:

instance Monad m => Arrow (Kleisli m) where ... 

widzimy, jak zrobić Monad część Arrow