2013-04-09 33 views
5

Tôi có một chức năngMemoizing IO tính toán trong Haskell

f :: MonadIO m => a -> m b 

mà mất một đầu vào và trả về một tính toán IO rằng sẽ mang lại kết quả. Tôi muốn "ghi nhớ" f để tôi chỉ thực hiện các tính toán này một lần cho mỗi đầu vào. Ví dụ, nếu

f :: String -> IO String 
f s = putStrLn ("hello " ++ s) >> return s 

sau đó tôi muốn có một chức năng memoize

do 
    mf <- memoize f 
    s <- mf "world" 
    t <- mf "world" 
    return (s,t) 

in "hello world" đúng một lần và trả ("world", "world"). Chương trình tôi đang viết là đa luồng, vì vậy thuộc tính này nên giữ ngay cả khi các luồng khác nhau đang gọi mf.

Dưới đây là giải pháp (khủng khiếp) mà tôi đã đưa ra cho đến thời điểm này. Câu hỏi của tôi là liệu nó có thể được cải thiện hay không.

memoize :: (MonadIO m, Ord a) => (a -> m b) -> m (a -> m b) 
memoize f = do 
    cache <- liftIO $ newTVarIO Map.empty 
    return $ \a -> do 
       v <- liftIO $ atomically $ lookupInsert cache a 
       b <- maybe (f a) return =<< liftIO (atomically $ takeTMVar v) 
       liftIO $ atomically $ putTMVar v $ Just b 
       return b 
    where 
     lookupInsert :: Ord a => TVar (Map a (TMVar (Maybe b))) -> a -> STM (TMVar (Maybe b)) 
     lookupInsert cache a = do 
         mv <- Map.lookup a <$> readTVar cache 
         case mv of 
          Just v -> return v 
          Nothing -> do 
            v <- newTMVar Nothing 
            modifyTVar cache (Map.insert a v) 
            return v 

Có một vài điều xảy ra ở đây:

1) cache có kiểu TVar (Map a (TMVar (Maybe b))). Nó ánh xạ đầu vào của TMVar có chứa giá trị được tính hoặc Nothing (cho biết giá trị chưa được tính). Hàm lookupInsert kiểm tra cache và chèn TMVar mới được khởi tạo thành Nothing nếu chưa có mặt hàng nào.

2) Hành động trở lại lần đầu tiên có được những v :: TMVar (Maybe b) liên quan đến a, sau đó mất nó, và một trong hai thực hiện việc tính toán f a để có được kết quả hoặc trả về giá trị được lưu trữ trong Maybe nếu nó có sẵn. Mẫu takeput này sao cho hai luồng khác nhau không chạy cả tính toán f a sau khi thấy rằng nó chưa được chạy.

+0

Tôi nghĩ rằng bạn muốn các giá trị trong bản đồ là 'TVar (Có thể a)' hoặc 'TMVar b' (tương đương với' TVar (Có thể b) 'dưới mui xe). Bạn có hai lớp trống rỗng khi có vẻ như bạn chỉ cần một lớp. Thứ hai, bạn nên kết hợp tất cả các hành động 'nguyên tử' của bạn thành một giao dịch duy nhất để tránh điều kiện chủng tộc. –

+0

Tôi không chắc chắn cách kết hợp các hành động 'nguyên tử' vì chúng ta có thể phải tính toán' f a' ở giữa. Cái sau tồn tại trong đơn nguyên 'm', vì vậy có vẻ như' take' và 'put' phải được nâng lên' m'. – davidsd

+0

Có vẻ như bạn đang sai lầm với tôi. – leftaroundabout

Trả lời

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