2013-11-23 15 views
7

Haskell Control.Arrowのドキュメントでは、Kleisliの矢印とモナドの関係について語っていますが、これをどのように使用するかはわかりません。私はIOモナドを含むそれ以外の矢印と合っていると思う関数を持っているので、Kleisliの矢印が役立つかもしれないと思う。Kleisli arrows with monadsの使い方は?

ディレクトリの元のファイル名と変更されたファイル名のペアを返す次の関数を実行します。

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 

私はそれを引き出すために持っていた場合、それはこのようなものになるだろう:

enter image description here

私はそれがKleisli矢印の使用の恩恵を受けることができると思うが、私は方法がわかりません。誰も指導を提供できますか?

答えて

6

datedFilesと同等である(>=>)。ここで

はリストにmapまたはzipを使用していない可能な実装です:

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) 

おそらく、それが最も直感的な実装ではありません。

Kleisli矢印のモナドはListT IOですが、唯一の非決定性はgetDirectoryContentsです。

最後の行は純粋な関数であることに注意してください。最後の行の(&&&)は、関数にArrowインスタンスを使用しています。

編集:lensパッケージからWrapped型クラスは、もう少し簡潔のnewtypeラッパーを追加/削除するために使用することができます。前の例にそれを適用すると、我々はで終わる:

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

Monadsは、Haskellのタイプと機能のカテゴリであるHaskから、Hask --- endofunctorへの、Functorです。つまり、Haskの矢印の一部は、 mの場合はa -> m bのように見えます。特定のモナドmの場合、矢印がa -> m bのように見えるHaskのサブカテゴリは、mのKleisliカテゴリです。

私たちは、アイデンティティが、我々はreturnjoinの両方を使用Monad ---であることをこれを必要とする理由である

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

のように定義されreturn :: a -> m aと組成(>>>) :: (a -> m b) -> (b -> m c) -> (a -> m c)矢印がありますので、それがカテゴリであることを知っています。


ハスケルでは、通常はサブカテゴリを持つことはできませんが、代わりにnewtypeが使用されます。

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) 

そして、我々は、Kleisli m a b sのカテゴリー内の矢印をタイプMonad m => a -> m bの機能をアップグレードし、(.)

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

でそれらを構成することができます一般的にそれはしかし、少し構文的にうるさいです。 newtypeはと(.)をオーバーロードするためにCategoryタイプクラッドを使用するためにのみ有益です。代わりに、それはあなたが表示されます可能性が高いですreturnとあなたの図が示すように、情報が「固定パイプライン」に流れるための矢印を使用して実装することができ

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

まず私は、リストを扱うから個々のファイルを扱う分割するために、あなたをお勧めしたいです。あなたの例では、timestampは他のすべてが純粋な関数なので、興味深い矢印です。それにもかかわらず、私たちはそれらのいくつかを矢印で作って、その例をより面白くすることができます。我々はKleisli矢印のような1つのファイル名を計算書き換えることができarrow notationを使用する:

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

、今のは Kleisli

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

を見てみましょう:

{-# 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

はモナドからの主な機能を覚えてみましょうKleisliはラッパーで、runKleisli - newtypeのアンラッパーです。

共通点は何ですか? a -> m b一部

とのは、インスタンス宣言を見てみましょう:

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

我々が見る、Arrow

Monad一部を作成する方法
関連する問題