2013-11-23 25 views
7

Trong tài liệu Haskell Control.Arrow nó nói về mối quan hệ mũi tên Kleisli 'với monads, nhưng nó không phải là rõ ràng với tôi làm thế nào để sử dụng này. Tôi có một chức năng mà tôi nghĩ phù hợp với mũi tên ngoại trừ nó liên quan đến đơn nguyên IO, vì vậy tôi nghĩ rằng mũi tên Kleisli có thể giúp đỡ.Làm thế nào để sử dụng mũi tên Kleisli với monads?

Thực hiện chức năng sau đây trả về các cặp tên tệp gốc và sửa đổi của một thư mục.

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 

Nếu tôi phải vẽ nó ra, nó sẽ là một cái gì đó như thế này:

enter image description here

Tôi nghĩ rằng nó có thể được hưởng lợi từ việc sử dụng các mũi tên Kleisli, nhưng tôi không biết làm thế nào . Bất cứ ai có thể cung cấp hướng dẫn?

Trả lời

6

datedFiles thể được thực hiện bằng mũi tên vì luồng thông tin trong một "đường ống cố định", như chương trình sơ đồ của bạn.

Dưới đây là một thực thể mà không sử dụng map hoặc zip trên danh sách:

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) 

Có thể cho rằng, nó không phải là việc thực hiện trực quan nhất.

Đơn nguyên cho mũi tên Kleisli là ListT IO, mặc dù điều không xác định duy nhất là do getDirectoryContents.

Lưu ý rằng dòng cuối cùng là hàm thuần túy; (&&&) cho dòng cuối cùng là sử dụng thể hiện Arrow cho các hàm.

Chỉnh sửa: Các Wrapped typeclass từ gói lens có thể được sử dụng để thêm/xóa trình bao bọc newtype một chút ngắn gọn hơn. Áp dụng nó cho ví dụ trước, chúng tôi kết thúc bằng:

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 là Functor s từ Hask, loại Haskell loại và chức năng, để Hask --- một endofunctor. Điều đó có nghĩa là một số mũi tên trong Hask trông giống như a -> m b đối với một số Monadm. Đối với một đơn vị cụ thể m, danh mục con của Hask nơi mũi tên trông giống như a -> m b là danh mục Kleisli cho m.

Chúng tôi biết đó là một loại bởi vì có một bản sắc mũi tên return :: a -> m a và thành phần (>>>) :: (a -> m b) -> (b -> m c) -> (a -> m c) định nghĩa như thế

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

đó là lý do chúng ta cần điều này là một Monad --- chúng tôi sử dụng cả hai returnjoin.


Trong Haskell, chúng tôi không thể chỉ có một danh mục con bình thường, mà thay vào đó sử dụng 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) 

Và sau đó chúng ta có thể nâng cấp chức năng của loại Monad m => a -> m b để Kleisli m a b s, mũi tên trong một thể loại, và soạn chúng với (.)

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

Nói chung đó là một bit về cú pháp ồn ào, mặc dù. Loại mới chỉ có giá trị để sử dụng Category typeclass để quá tải id(.). Thay vào đó, nhiều khả năng bạn sẽ thấy return(>=>) đó tương đương với

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

Trước tiên, tôi khuyên bạn nên chia xử lý một tệp riêng lẻ để xử lý danh sách. Trong ví dụ của bạn, timestamp là mũi tên thú vị, bởi vì tất cả những người khác là các hàm thuần túy. Tuy nhiên, chúng ta có thể biến chúng thành mũi tên để làm ví dụ thú vị hơn. Sử dụng arrow notation chúng ta có thể viết lại máy tính một tên tập tin như một mũi tên Kleisli:

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

Hãy nhớ chức năng chính từ Monad:

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

và bây giờ chúng ta hãy nhìn vào Kleisli

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

nơi Kleisli là một trình bao bọc và runKleisli - unwrapper từ newtype.

Điểm chung là gì? a -> m b phần

Và chúng ta hãy nhìn vào tuyên bố Ví dụ:

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

chúng ta thấy, làm thế nào để làm cho một phần của MonadArrow

Các vấn đề liên quan