packages feed

control-monad-loop (empty) → 0.1

raw patch · 8 files changed

+423/−0 lines, 8 filesdep +basedep +transformersdep +transformers-basesetup-changed

Dependencies added: base, transformers, transformers-base

Files

+ Control/Monad/Trans/Loop.hs view
@@ -0,0 +1,189 @@+-- |+-- Module       : Control.Monad.Trans.Loop+-- Copyright    : (c) Joseph Adams 2012+-- License      : BSD3+-- Maintainer   : joeyadams3.14159@gmail.com+--++{-# LANGUAGE Rank2Types #-}++-- Needed for the MonadBase instance+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-}++module Control.Monad.Trans.Loop (+    -- * The LoopT monad transformer+    LoopT(..),+    stepLoopT,++    -- * continue and exit+    continue,+    exit,+    continueWith,+    exitWith,++    -- * Looping constructs+    foreach,+    while,+    doWhile,+    once,+    repeatLoopT,+    iterateLoopT,++    -- * Lifting other operations+    liftLocalLoopT,+) where++import Control.Applicative          (Applicative(pure, (<*>)))+import Control.Monad.Base           (MonadBase(liftBase), liftBaseDefault)+import Control.Monad.IO.Class       (MonadIO(liftIO))+import Control.Monad.Trans.Class    (MonadTrans(lift))++-- | 'LoopT' is a monad transformer for the loop body.  It provides two+-- capabilities:+--+--  * 'continue' to the next iteration.+--+--  * 'exit' the whole loop.+newtype LoopT c e m a = LoopT+    { runLoopT :: forall r.     -- This universal quantification forces the+                                -- LoopT computation to call one of the+                                -- following continuations.+                  (c -> m r)    -- continue+               -> (e -> m r)    -- exit+               -> (a -> m r)    -- return a value+               -> m r+    }++instance Functor (LoopT c e m) where+    fmap f m = LoopT $ \next fin cont -> runLoopT m next fin (cont . f)++instance Applicative (LoopT c e m) where+    pure a    = LoopT $ \_    _   cont -> cont a+    f1 <*> f2 = LoopT $ \next fin cont ->+                runLoopT f1 next fin $ \f ->+                runLoopT f2 next fin (cont . f)++instance Monad (LoopT c e m) where+    return a = LoopT $ \_    _   cont -> cont a+    m >>= k  = LoopT $ \next fin cont ->+               runLoopT m next fin $ \a ->+               runLoopT (k a) next fin cont++instance MonadTrans (LoopT c e) where+    lift m = LoopT $ \_ _ cont -> m >>= cont++instance MonadIO m => MonadIO (LoopT c e m) where+    liftIO = lift . liftIO++instance MonadBase b m => MonadBase b (LoopT c e m) where+    liftBase = liftBaseDefault++-- | Call a loop body, passing it a continuation for the next iteration.+-- This can be used to construct custom looping constructs.  For example,+-- here is the definition of 'foreach':+--+-- >foreach list body = loop list+-- >  where loop []     = return ()+-- >        loop (x:xs) = stepLoopT (body x) (\_ -> loop xs)+stepLoopT :: Monad m => LoopT c e m c -> (c -> m e) -> m e+stepLoopT body next = runLoopT body next return next++------------------------------------------------------------------------+-- continue and exit+++-- | Skip the rest of the loop body and go to the next iteration.+continue :: LoopT () e m a+continue = continueWith ()++-- | Break out of the loop entirely.+exit :: LoopT c () m a+exit = exitWith ()++-- | Like 'continue', but return a value from the loop body.+continueWith :: c -> LoopT c e m a+continueWith c = LoopT $ \next _ _ -> next c++-- | Like 'exit', but return a value from the loop as a whole.+-- See the documentation of 'iterateLoopT' for an example.+exitWith :: e -> LoopT c e m a+exitWith e = LoopT $ \_ fin _ -> fin e+++------------------------------------------------------------------------+-- Looping constructs+++-- | Call the loop body with each item in the list.+--+-- If you do not need to 'continue' or 'exit' the loop, consider using+-- 'Control.Monad.forM_' instead.+foreach :: Monad m => [a] -> (a -> LoopT c () m c) -> m ()+foreach list body = loop list+  where loop []     = return ()+        loop (x:xs) = stepLoopT (body x) (\_ -> loop xs)++-- | Repeat the loop body while the predicate holds.  Like a @while@ loop in C,+-- the condition is tested first.+while :: Monad m => m Bool -> LoopT c () m c -> m ()+while cond body = loop+  where loop = do b <- cond+                  if b then stepLoopT body (\_ -> loop)+                       else return ()++-- | Like a @do while@ loop in C, where the condition is tested after+-- the loop body.+--+-- 'doWhile' returns the result of the last iteration.  This is possible+-- because, unlike 'foreach' and 'while', the loop body is guaranteed to be+-- executed at least once.+doWhile :: Monad m => LoopT a a m a -> m Bool -> m a+doWhile body cond = loop+  where loop = stepLoopT body $ \a -> do+            b <- cond+            if b then loop+                 else return a++-- | Execute the loop body once.  This is a convenient way to introduce early+-- exit support to a block of code.+--+-- 'continue' and 'exit' do the same thing inside of 'once'.+once :: Monad m => LoopT a a m a -> m a+once body = runLoopT body return return return++-- | Execute the loop body again and again.  The only way to exit 'repeatLoopT'+-- is to call 'exit' or 'exitWith'.+repeatLoopT :: Monad m => LoopT c e m a -> m e+repeatLoopT body = loop+  where loop = runLoopT body (\_ -> loop) return (\_ -> loop)++-- | Call the loop body again and again, passing it the result of the previous+-- iteration each time around.  The only way to exit 'iterateLoopT' is to call+-- 'exit' or 'exitWith'.+--+-- Example:+--+-- >count :: Int -> IO Int+-- >count n = iterateLoopT 0 $ \i ->+-- >    if i < n+-- >        then do+-- >            lift $ print i+-- >            return $ i+1+-- >        else exitWith i+iterateLoopT :: Monad m => c -> (c -> LoopT c e m c) -> m e+iterateLoopT z body = loop z+  where loop c = stepLoopT (body c) loop+++------------------------------------------------------------------------+-- Lifting other operations+++-- | Lift a function like 'Control.Monad.Trans.Reader.local' or+-- 'Control.Exception.mask_'.+liftLocalLoopT :: Monad m => (forall a. m a -> m a) -> LoopT c e m b -> LoopT c e m b+liftLocalLoopT f cb = LoopT $ \next fin cont -> do+    m <- f $ runLoopT cb (return . next) (return . fin) (return . cont)+    m
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012, Joseph Adams++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Joseph Adams nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ control-monad-loop.cabal view
@@ -0,0 +1,36 @@+name:               control-monad-loop+version:            0.1+synopsis:           Simple monad transformer for imperative-style loops+description:+    A library of looping constructs with @continue@ and @exit@ control flow+    statements.+homepage:           https://github.com/joeyadams/haskell-control-monad-loop+bug-reports:        https://github.com/joeyadams/haskell-control-monad-loop/issues+license:            BSD3+license-file:       LICENSE+author:             Joey Adams+maintainer:         joeyadams3.14159@gmail.com+copyright:          Copyright (c) Joseph Adams 2012+category:           Control+build-type:         Simple+cabal-version:      >=1.8++extra-source-files:+    test/leak.hs+    test/liftLocal.hs+    test/lift-continue.hs+    test/recycled-numbers.hs++source-repository head+    type:       git+    location:   git://github.com/joeyadams/haskell-control-monad-loop++library+    exposed-modules:+        Control.Monad.Trans.Loop++    build-depends   : base >= 4 && < 5+                    , transformers+                    , transformers-base++    ghc-options: -Wall -fwarn-tabs
+ test/leak.hs view
@@ -0,0 +1,28 @@+-- Make sure basic loops don't leak memory++import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.Loop+import Control.Monad.Trans.State.Strict+import Data.Int (Int64)++count :: Int64 -> IO Int64+count n = iterateLoopT 0 $ \i ->+    if i < n+        then return $! i+1+        else exitWith i++sumLoop :: [Int64] -> Int64+sumLoop list =+    flip execState 0 $ foreach list $ \i -> do+        when (i == 10000000) exit+        lift $ modify' (+i)+  where+    modify' f = do+        x <- get+        put $! f x++main :: IO ()+main = do+    count 100000000 >>= print+    print $ sumLoop [1..10000000] + 10000000
+ test/lift-continue.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE ScopedTypeVariables #-}+import Control.Monad+import Control.Monad.Base+import Control.Monad.Trans.Class+import Control.Monad.Trans.Loop++main :: IO ()+main = do+    foreach [1..10] $ \(i :: Int) -> do+        foreach [1..10] $ \(j :: Int) -> do+            when (j > i) $+                lift continue+            when (i == 2 && j == 2) $+                exit+            when (i == 9 && j == 9) $+                lift exit+            liftBase $ print (i, j)+        liftBase $ putStrLn "Inner loop finished"+    putStrLn "Outer loop finished"
+ test/liftLocal.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE ScopedTypeVariables #-}+import Prelude hiding (log)++import Control.Monad.Trans.Loop++import Control.Exception+import Control.Monad.Reader+import Control.Monad.Writer++test1 :: IO ()+test1 =+    foreach [1..4] $ \(i :: Int) -> do+        let log msg = liftIO $ putStrLn $ "test1: " ++ show i ++ ": " ++ msg++            logMaskingState = do+                b <- lift getMaskingState+                log $ "getMaskingState: " ++ show b++        logMaskingState++        liftLocalLoopT mask_ $ do+            logMaskingState+            when (i == 3) $ do+                log "continue"+                continue+            logMaskingState++-- This test is interesting because we're using mtl's 'local', which in this+-- context walks up the WriterT too.+test2 :: IO ()+test2 =+    mapM_ putStrLn $+    flip runReader (0 :: Int) $+    execWriterT $+    foreach [1..4] $ \(i :: Int) -> do+        let log msg = lift $ tell ["test2: " ++ show i ++ ": " ++ msg]++            logAsk = do+                n <- lift ask+                log $ "ask: " ++ show n++        logAsk++        liftLocalLoopT (local (+1)) $ do+            logAsk+            when (i == 3) $ do+                log "continue"+                continue+            logAsk
+ test/recycled-numbers.hs view
@@ -0,0 +1,70 @@+-- This solves Google Code Jam 2012 Qualification Problem C "Recycled Numbers" [1].+-- The problem is: given a range of numbers with the same number of digits,+-- count how many pairs of them are the same modulo rotation of digits.+--+--  [1]: http://code.google.com/codejam/contest/1460488/dashboard#s=p2+{-# LANGUAGE ScopedTypeVariables #-}+import Control.Monad.Trans.Loop++import Control.Applicative          ((<$>))+import Control.Monad+import Control.Monad.ST+import Control.Monad.Trans.Class+import Data.Array.ST+import Data.STRef++recycledNumbers :: (Int, Int) -> Int+recycledNumbers (lb, ub)+    | not (1 <= lb && lb <= ub && factor == rotateFactor ub)+    = error "recycledNumbers: invalid bounds"+    | otherwise = runST $ do+        bmp   <- newArray (lb, ub) False :: ST s (STUArray s Int Bool)+        total <- newSTRef 0+        forM_ [lb..ub] $ \i -> do+            count <- newSTRef 0+            foreach (iterate rotate i) $ \j -> do+                when (not $ j >= i && j <= ub)+                    continue+                whenM (lift $ readArray bmp j)+                    exit+                lift $ writeArray bmp j True+                lift $ modifySTRef' count (+1)+            readSTRef count >>= modifySTRef' total . (+) . numPairs+        readSTRef total+  where+    factor = rotateFactor lb++    rotate x = let (n, d) = x `divMod` 10+                in d*factor + n++    numPairs n = (n-1) * n `div` 2++main :: IO ()+main = do+    t <- readLn+    forM_ [1..t] $ \(x :: Int) -> do+        [a, b] <- map read . words <$> getLine+        let y = recycledNumbers (a, b)+        putStrLn $ "Case #" ++ show x ++ ": " ++ show y++------------------------------------------------------------------------+-- Helper functions++-- | Return the power of 10 corresponding to the most significant digit in the+-- number.+rotateFactor :: Int -> Int+rotateFactor n | n < 1     = error "rotateFactor: n < 1"+               | otherwise = loop 1+  where+    loop p | p' > n    = p+           | p' < p    = p     -- in case of overflow+           | otherwise = loop p'+      where p' = p * 10++modifySTRef' :: STRef s a -> (a -> a) -> ST s ()+modifySTRef' ref f = do+    x <- readSTRef ref+    writeSTRef ref $! f x++whenM :: Monad m => m Bool -> m () -> m ()+whenM p m = p >>= \b -> if b then m else return ()