From 60f518f8fd581d2a72c0e840646b075299ddf659 Mon Sep 17 00:00:00 2001 From: Tom Ellis Date: Fri, 27 Sep 2024 13:18:32 +0100 Subject: [PATCH] Panics --- bluefin-internal/bluefin-internal.cabal | 9 +- bluefin-internal/src/Bluefin/Internal.hs | 1337 +---------------- .../src/Bluefin/Internal/Examples.hs | 746 --------- .../src/Bluefin/Internal/Pipes.hs | 267 ---- 4 files changed, 2 insertions(+), 2357 deletions(-) delete mode 100644 bluefin-internal/src/Bluefin/Internal/Examples.hs delete mode 100644 bluefin-internal/src/Bluefin/Internal/Pipes.hs diff --git a/bluefin-internal/bluefin-internal.cabal b/bluefin-internal/bluefin-internal.cabal index 4dc5e69..848f098 100644 --- a/bluefin-internal/bluefin-internal.cabal +++ b/bluefin-internal/bluefin-internal.cabal @@ -77,18 +77,11 @@ library default-language: Haskell2010 hs-source-dirs: src build-depends: - async, base >= 4.12 && < 4.21, - unliftio-core < 0.3, - transformers < 0.7, - transformers-base < 0.5, - monad-control < 1.1, linear-base ghc-options: -Wall exposed-modules: - Bluefin.Internal, - Bluefin.Internal.Examples, - Bluefin.Internal.Pipes + Bluefin.Internal test-suite bluefin-test import: defaults diff --git a/bluefin-internal/src/Bluefin/Internal.hs b/bluefin-internal/src/Bluefin/Internal.hs index 87ffbc6..2d7488a 100644 --- a/bluefin-internal/src/Bluefin/Internal.hs +++ b/bluefin-internal/src/Bluefin/Internal.hs @@ -1,37 +1,9 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE DerivingVia #-} {-# LANGUAGE LinearTypes #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE QualifiedDo #-} -{-# LANGUAGE RoleAnnotations #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE UnboxedTuples #-} -{-# LANGUAGE UnliftedNewtypes #-} -{-# OPTIONS_HADDOCK not-home #-} module Bluefin.Internal where -import qualified Control.Concurrent.Async as Async -import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar) -import Control.Exception (throwIO, tryJust) -import qualified Control.Exception -import qualified Control.Functor.Linear as L -import Control.Monad.Base (MonadBase (liftBase)) -import Control.Monad.IO.Class (MonadIO, liftIO) -import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO) -import Control.Monad.Trans.Control (MonadBaseControl, StM, liftBaseWith, restoreM) -import qualified Control.Monad.Trans.Reader as Reader -import Data.Foldable (for_) -import qualified Data.Functor.Linear as DL -import Data.IORef (IORef, newIORef, readIORef, writeIORef) -import Data.Kind (Type) -import qualified Data.Unique -import GHC.Exts (Proxy#, proxy#) -import System.IO.Unsafe (unsafePerformIO) -import Unsafe.Coerce (unsafeCoerce) import qualified Unsafe.Linear -import Prelude hiding (drop, head, read, return) +import Prelude data Effects = Union Effects Effects @@ -42,582 +14,17 @@ infixr 9 :& type (:&) = Union -type role Eff nominal representational - newtype Eff (es :: Effects) a = UnsafeMkEff {unsafeUnEff :: IO a} - deriving stock (Functor) - deriving newtype (Applicative, Monad) - --- | Because doing 'IO' operations inside 'Eff' requires a value-level --- argument we can't give @IO@-related instances to @Eff@ directly. --- Instead we wrap it in @EffReader@. -newtype EffReader r es a = MkEffReader {unEffReader :: r -> Eff es a} - deriving (Functor, Applicative, Monad) via (Reader.ReaderT r (Eff es)) - -instance (e :> es) => MonadIO (EffReader (IOE e) es) where - liftIO = MkEffReader . flip effIO - -effReader :: (r -> Eff es a) -> EffReader r es a -effReader = MkEffReader - -runEffReader :: r -> EffReader r es a -> Eff es a -runEffReader r (MkEffReader m) = m r - --- This is possibly what @withRunInIO@ should morally be. -withEffToIO :: - (e2 :> es) => - -- | Continuation with the unlifting function in scope. - ((forall r. (forall e1. IOE e1 -> Eff (e1 :& es) r) -> IO r) -> IO a) -> - IOE e2 -> - Eff es a -withEffToIO k io = effIO io (k (\f -> unsafeUnEff (f MkIOE))) - -withEffToIO' :: - (e2 :> es) => - -- | Continuation with the unlifting function in scope. - IOE e2 -> - ((forall r. (forall e1. IOE e1 -> Eff (e1 :& es) r) -> IO r) -> IO a) -> - Eff es a -withEffToIO' io k = withEffToIO k io - --- We don't try to do anything sophisticated here. I haven't thought --- through all the consequences. -instance (e :> es) => MonadUnliftIO (EffReader (IOE e) es) where - withRunInIO :: - ((forall a. EffReader (IOE e) es a -> IO a) -> IO b) -> - EffReader (IOE e) es b - withRunInIO k = - MkEffReader - ( UnsafeMkEff - . Reader.runReaderT - ( withRunInIO - ( \f -> - k - ( f - . Reader.ReaderT - . (unsafeUnEff .) - . unEffReader - ) - ) - ) - ) - -race :: - (e2 :> es) => - (forall e. IOE e -> Eff (e :& es) a) -> - (forall e. IOE e -> Eff (e :& es) a) -> - IOE e2 -> - Eff es a -race x y io = do - r <- withEffToIO' io $ \toIO -> - Async.race (toIO x) (toIO y) - - pure $ case r of - Left a -> a - Right a -> a - -concurrently_ :: - (e2 :> es) => - (forall e. IOE e -> Eff (e :& es) a) -> - (forall e. IOE e -> Eff (e :& es) b) -> - IOE e2 -> - Eff es () -concurrently_ x y io = do - withEffToIO' io $ \toIO -> - Async.concurrently_ (toIO x) (toIO y) - --- | Connect two coroutines. Their execution is interleaved by --- exchanging @a@s and @b@s. When the first yields its first @a@ it --- starts the second (which is waiting to receive an @a@). -connectCoroutines :: - forall es a b r. - (forall e. Coroutine a b e -> Eff (e :& es) r) -> - (forall e. a -> Coroutine b a e -> Eff (e :& es) r) -> - -- | ͘ - Eff es r -connectCoroutines m1 m2 = unsafeProvideIO $ \io -> do - av <- effIO io newEmptyMVar - bv <- effIO io newEmptyMVar - - let t1 :: forall e. IOE e -> Eff (e :& es) r - t1 io' = forEach (useImplWithin m1) $ \a -> effIO io' $ do - putMVar av a - takeMVar bv - - let t2 :: forall e. IOE e -> Eff (e :& es) r - t2 io' = do - ainit <- effIO io' (takeMVar av) - forEach (useImplWithin (m2 ainit)) $ \b_ -> effIO io' $ do - putMVar bv b_ - takeMVar av - - race (useImplWithin t1) (useImplWithin t2) io - -newtype Linearly a b r (e :: Effects) - = UnsafeMkLinearly (Ur (MVar a, MVar (Either b r))) - -data Ur a where - Ur :: a -> Ur a - -yieldLinearly :: - (e :> es) => - Linearly a b r e %1 -> - a -> - Eff es (Either (Ur b, Linearly a b r e) (Ur r)) -yieldLinearly (UnsafeMkLinearly (Ur (av, bv))) a = UnsafeMkEff $ do - putMVar av a - takeMVar bv >>= \case - Left b_ -> pure (Left (Ur b_, UnsafeMkLinearly (Ur (av, bv)))) - Right r -> pure (Right (Ur r)) - -instance DL.Functor (Eff es) where - fmap = unsafeCoerce (fmap @IO) - -instance L.Functor (Eff es) where - fmap = unsafeCoerce (fmap @IO) - -instance DL.Applicative (Eff es) where - pure = unsafeCoerce (pure @IO) - (<*>) = unsafeCoerce ((<*>) @IO) - -instance L.Applicative (Eff es) where - pure = unsafeCoerce (pure @IO) - (<*>) = unsafeCoerce ((<*>) @IO) - -instance L.Monad (Eff es) where - (>>=) = unsafeCoerce ((>>=) @IO) - -newtype Wrap1 a b es r - = Wrap1 (forall e. a -> Coroutine b a e -> Eff (e :& es) r) - -newtype Wrap2 a b es r r' - = Wrap2 (forall e. Linearly a b r e %1 -> Eff (e :& es) r') - -linearly :: - forall es a b r r'. - (forall e. a -> Coroutine b a e -> Eff (e :& es) r) %1 -> - (forall e. Linearly a b r e %1 -> Eff (e :& es) r') %1 -> - Eff es r' -linearly x y = linearlyWrapL (Wrap1 x) (Wrap2 y) - -linearlyWrapL :: - forall es a b r r'. - Wrap1 a b es r %1 -> - Wrap2 a b es r r' %1 -> - Eff es r' -linearlyWrapL x = - Unsafe.Linear.toLinear ((Unsafe.Linear.toLinear linearlyWrap) x) - -linearlyWrap :: - forall es a b r r'. - Wrap1 a b es r -> - Wrap2 a b es r r' -> - Eff es r' -linearlyWrap (Wrap1 w1) (Wrap2 w2_) = linearlyImpl w1 w2_ - -linearlyImpl :: - forall es a b r r'. - (forall e. a -> Coroutine b a e -> Eff (e :& es) r) -> - (forall e. Linearly a b r e %1 -> Eff (e :& es) r') -> - Eff es r' -linearlyImpl m1 m2 = unsafeProvideIO $ \io -> do - av <- effIO io newEmptyMVar - bv <- effIO io newEmptyMVar - rv <- effIO io newEmptyMVar - - let t1 :: forall e. IOE e -> Eff (e :& es) () - t1 io' = do - ainit <- effIO io' (takeMVar av) - r <- forEach (useImplWithin (m1 ainit)) $ \b_ -> effIO io' $ do - putMVar bv (Left b_) - takeMVar av - - effIO io' (putMVar bv (Right r)) - - let t2 :: forall e. IOE e -> Eff (e :& es) () - t2 io' = do - r <- m2 (UnsafeMkLinearly (Ur (av, bv))) - effIO io' (putMVar rv r) - - concurrently_ (useImplWithin t1) (useImplWithin t2) io - - -- I don't really like returning through rv. It would be good if we - -- could tell t1 to be "done" and block forever without returning. - effIO io (takeMVar rv) - -zipLinearly :: - (e1 :> es, e2 :> es, e3 :> es) => - Linearly a b1 r1 e1 %1 -> - Linearly a b2 r2 e2 %1 -> - a -> - Coroutine (b1, b2) a e3 -> - Eff es (Either (r1, Linearly a b2 r2 e2) (r2, b1, Linearly a b1 r1 e1)) -zipLinearly l1 l2 a c = L.do - yieldLinearly l1 a L.>>= \case - Right (Ur r1) -> L.pure (Left (r1, l2)) - Left (Ur b1, l1') -> - yieldLinearly l2 a L.>>= \case - Right (Ur r2) -> L.pure (Right (r2, b1, l1')) - Left (Ur b2_, l2') -> L.do - Ur a' <- Ur <$> yieldCoroutine c (b1, b2_) - zipLinearly l1' l2' a' c - -zipLinearly' :: - (e1 :> es, e2 :> es) => - Linearly a b1 r1 e1 %1 -> - Linearly a b2 r2 e2 %1 -> - ( forall (e :: Effects). - Linearly - a - (b1, b2) - (Either (r1, Linearly a b2 r2 e2) (r2, b1, Linearly a b1 r1 e1)) - e %1 -> - Eff (e :& es) r - ) %1 -> - Eff es r -zipLinearly' l1 l2 a = linearly (zipLinearly l1 l2) a - -zipLinearlyExample :: IO () -zipLinearlyExample = runEff $ \io -> do - let m1 y = for_ [1 .. 10 :: Int] (yield y) - - let m2 y = for_ [1 .. 5 :: Int] (yield y) - - linearly (\() -> m1) \l1 -> L.do - linearly (\() -> m2) \l2 -> L.do - zipLinearly' l1 l2 \l3 -> L.do - foo io l3 - -foo :: - (e :> es, Show a, e1 :> es, Show r1, Show r2, e2 :> es, Show a1, Show a2, e4 :> es) => - IOE e4 -> - Linearly - () - a - ( Either - (r1, Linearly () a2 r2 e2) - (r2, a1, Linearly () a1 r1 e1) - ) - e %1 -> - Eff es () -foo io l3 = L.do - catchL - ( \exn2 -> do - catchL - ( \exn -> do - foreverL l3 \l3' -> do - yieldLinearly l3' () L.>>= \case - Left (Ur bs, l3'') -> L.do - effIO io (print bs) - L.pure l3'' - Right (Ur r) -> case r of - Left (r1, l2') -> L.do - effIO io (print r1) - throwL exn l2' - Right (r2, a1, l1') -> L.do - effIO io $ do - print a1 - print r2 - throwL exn2 l1' - ) - (bar io) - ) - (bar io) - -forL_ :: (L.Monad m) => [a] -> s %1 -> (a -> s %1 -> m s) -> m s -forL_ [] s _ = L.pure s -forL_ (x : xs) s f = L.do - s' <- f x s - forL_ xs s' f - -foreverL :: (L.Monad m) => s %1 -> (s %1 -> m s) -> m void -foreverL s f = L.do - s' <- f s - foreverL s' f - -bar :: - (e :> es, e1 :> es, Show a, Show r) => - IOE e1 -> - Linearly () a r e %1 -> - Eff es () -bar io l = L.do - catchL - ( \exn -> do - foreverL l \l' -> do - yieldLinearly l' () L.>>= \case - Left (Ur a, l'') -> L.do - effIO io (print a) - L.pure l'' - Right r -> throwL exn r - ) - (\(Ur r) -> effIO io (print r)) - -receiveStream :: - (forall e. Coroutine () a e -> Eff (e :& es) r) -> - (forall e. Stream a e -> Eff (e :& es) r) -> - Eff es r -receiveStream r s = connectCoroutines r (\() -> s) - -instance (e :> es) => MonadBase IO (EffReader (IOE e) es) where - liftBase = liftIO - -instance (e :> es) => MonadBaseControl IO (EffReader (IOE e) es) where - type StM (EffReader (IOE e) es) a = a - liftBaseWith = withRunInIO - restoreM = pure - -instance (e :> es) => MonadFail (EffReader (Exception String e) es) where - fail = MkEffReader . flip throw - -hoistReader :: - (forall b. m b -> n b) -> - Reader.ReaderT r m a -> - Reader.ReaderT r n a -hoistReader f = Reader.ReaderT . (\m -> f . Reader.runReaderT m) - --- | Run `MonadIO` operations in 'Eff'. --- --- @ --- >>> runEff $ \\io -> withMonadIO io $ liftIO $ do --- putStrLn "Hello world!" --- Hello, world! --- @ - --- This is not really any better than just running the action in --- `IO`. -withMonadIO :: - (e :> es) => - IOE e -> - -- | 'MonadIO' operation - (forall m. (MonadIO m) => m r) -> - -- | @MonadIO@ operation run in @Eff@ - Eff es r -withMonadIO io m = unEffReader m io - --- | Run 'MonadFail' operations in 'Eff'. --- --- @ --- >>> runPureEff $ try $ \\e -> --- when (2 > 1) $ --- withMonadFail e (fail "2 was bigger than 1") --- Left "2 was bigger than 1" --- @ - --- This is not really any better than just running the action in --- `Either String` and then applying `either (throw f) pure`. -withMonadFail :: - (e :> es) => - -- | @Exception@ to @throw@ on @fail@ - Exception String e -> - -- | 'MonadFail' operation - (forall m. (MonadFail m) => m r) -> - -- | @MonadFail@ operation run in @Eff@ - Eff es r -withMonadFail f m = unEffReader m f - -unsafeRemoveEff :: Eff (e :& es) a -> Eff es a -unsafeRemoveEff = UnsafeMkEff . unsafeUnEff - --- | Run an 'Eff' that doesn't contain any unhandled effects. -runPureEff :: (forall es. Eff es a) -> a -runPureEff e = unsafePerformIO (unsafeUnEff e) - -weakenEff :: t `In` t' -> Eff t r -> Eff t' r -weakenEff _ = UnsafeMkEff . unsafeUnEff - -insertFirst :: Eff b r -> Eff (c1 :& b) r -insertFirst = weakenEff (drop (eq (# #))) - -insertSecond :: Eff (c1 :& b) r -> Eff (c1 :& (c2 :& b)) r -insertSecond = weakenEff (b (drop (eq (# #)))) - -insertManySecond :: (b :> c) => Eff (c1 :& b) r -> Eff (c1 :& c) r -insertManySecond = weakenEff (bimap has has) - -assoc1Eff :: Eff ((a :& b) :& c) r -> Eff (a :& (b :& c)) r -assoc1Eff = weakenEff (assoc1 (# #)) - -pushFirst :: Eff a r -> Eff (a :& b) r -pushFirst = weakenEff (fstI (# #)) - -mergeEff :: Eff (a :& a) r -> Eff a r -mergeEff = weakenEff (merge (# #)) - -inContext :: (e2 :> e1) => Eff (e1 :& e2) r -> Eff e1 r -inContext = weakenEff (subsume1 has) - --- | Used to define dynamic effects. -useImpl :: (e :> es) => Eff e r -> Eff es r -useImpl = weakenEff has - --- | Used to define handlers of compound effects. -useImplIn :: - (e :> es) => - (t -> Eff (es :& e) r) -> - t -> - -- | ͘ - Eff es r -useImplIn f h = inContext (f h) - --- | Like 'useImplIn' -useImplWithin :: - (e :> es) => - (t -> Eff (e1 :& e) r) -> - t -> - -- | ͘ - Eff (e1 :& es) r -useImplWithin k fsh = insertManySecond (k fsh) - --- | Handle to a capability to create strict mutable state handles -data StateSource (e :: Effects) = StateSource -- | Handle to an exception of type @exn@ newtype Exception exn (e :: Effects) = UnsafeMkException (forall a. exn -> IO a) --- | A handle to a strict mutable state of type @s@ -newtype State s (e :: Effects) = UnsafeMkState (IORef s) - --- | A handle to a coroutine that yields values of type @a@ and then --- expects values of type @b@. -newtype Coroutine a b (e :: Effects) = MkCoroutine (a -> Eff e b) - --- | A handle to a stream that yields values of type @a@. It is --- implemented as a handle to a coroutine that yields values of type --- @a@ and then expects values of type @()@. -type Stream a = Coroutine a () - --- | You can define a @Handle@ instance for your compound handles. As --- an example, an "application" handle with a dynamic effect for --- database queries, a concrete effect for application state and a --- concrete effect for a logging effect might look like this: --- --- @ --- data Application e = MkApplication --- { queryDatabase :: String -> Int -> Eff e [String], --- applicationState :: State (Int, Bool) e, --- logger :: Stream String e --- } --- @ --- --- To define @mapHandle@ for @Application@ you should apply --- @mapHandle@ to all the fields that are themeselevs handles and --- apply @useImpl@ to all the fields that are dynamic effects: --- --- @ --- instance Handle Application where --- mapHandle --- MkApplication --- { queryDatabase = q, --- applicationState = a, --- logger = l --- } = --- MkApplication --- { queryDatabase = (fmap . fmap) useImpl q, --- applicationState = mapHandle a, --- logger = mapHandle l --- } --- @ --- --- Note that preceding @useImpl@ on the dynamic effect there is one --- fmap per @->@ that appears in type of the dynamic effect. That is, --- @queryDatabase@ has type @String -> Int -> Eff e [String]@, which --- has two @->@, so there are two @fmap@s before @useImpl@. -class Handle (h :: Effects -> Type) where - -- | Used to create compound effects, i.e. handles that contain - -- other handles. - mapHandle :: (e :> es) => h e -> h es - -instance Handle (State s) where - mapHandle (UnsafeMkState s) = UnsafeMkState s - -instance Handle (Exception s) where - mapHandle (UnsafeMkException s) = UnsafeMkException s - -instance Handle (Coroutine a b) where - mapHandle (MkCoroutine f) = MkCoroutine (fmap useImpl f) - -instance Handle (Writer w) where - mapHandle (Writer wr) = Writer (mapHandle wr) - -newtype In (a :: Effects) (b :: Effects) = In# (# #) - -merge :: (# #) -> (a :& a) `In` a -merge (# #) = In# (# #) - -eq :: (# #) -> a `In` a -eq (# #) = In# (# #) - -fstI :: (# #) -> a `In` (a :& b) -fstI (# #) = In# (# #) - -sndI :: (# #) -> a `In` (b :& a) -sndI (# #) = In# (# #) - -cmp :: a `In` b -> b `In` c -> a `In` c -cmp (In# (# #)) (In# (# #)) = In# (# #) - -bimap :: a `In` b -> c `In` d -> (a :& c) `In` (b :& d) -bimap (In# (# #)) (In# (# #)) = In# (# #) - -assoc1 :: (# #) -> ((a :& b) :& c) `In` (a :& (b :& c)) -assoc1 (# #) = In# (# #) - -drop :: a `In` b -> a `In` (c :& b) -drop h = w2 (b h) - -here :: a `In` b -> (a `In` (b :& c)) -here h = w (b2 h) - -w :: (a :& b) `In` c -> (a `In` c) -w = cmp (fstI (# #)) - -w2 :: (b :& a) `In` c -> (a `In` c) -w2 = cmp (sndI (# #)) - -b2 :: (a `In` b) -> ((a :& c) `In` (b :& c)) -b2 h = bimap h (eq (# #)) - -b :: (a `In` b) -> (c :& a) `In` (c :& b) -b = bimap (eq (# #)) - -subsume1 :: (e2 `In` e1) -> (e1 :& e2) `In` e1 -subsume1 i = cmp (bimap (eq (# #)) i) (merge (# #)) - -- | Effect subset constraint class (es1 :: Effects) :> (es2 :: Effects) --- | A set of effects @e@ is a subset of itself -instance {-# INCOHERENT #-} e :> e - --- | If @e@ is subset of @es@ then @e@ is a subset of a larger set, @x --- :& es@ -instance (e :> es) => e :> (x :& es) - --- Do we want this? --- instance {-# incoherent #-} (e :> es) => (e' :& e) :> (e' :> es) - --- This seems a bit wobbly - --- | @e@ is a subset of a larger set @e :& es@ -instance {-# INCOHERENT #-} e :> (e :& es) - --- | --- @ --- >>> runPureEff $ try $ \\e -> do --- throw e 42 --- pure "No exception thrown" --- Left 42 --- @ --- --- @ --- >>> runPureEff $ try $ \\e -> do --- pure "No exception thrown" --- Right "No exception thrown" --- @ throw :: (e :> es) => Exception ex e -> - -- | Value to throw ex -> Eff es a throw (UnsafeMkException throw_) e = UnsafeMkEff (throw_ e) @@ -625,748 +32,6 @@ throw (UnsafeMkException throw_) e = UnsafeMkEff (throw_ e) throwL :: (e :> es) => Exception ex e -> - -- | Value to throw ex %1 -> Eff es a throwL ex = Unsafe.Linear.toLinear (throw ex) - -has :: forall a b. (a :> b) => a `In` b -has = In# (# #) - -data Dict c where - Dict :: forall c. (c) => Dict c - --- Seems like it could be better -have :: forall a b. a `In` b -> Dict (a :> b) -have = unsafeCoerce (Dict @(a :> (a :& b))) - --- | --- @ --- >>> runPureEff $ try $ \\e -> do --- throw e 42 --- pure "No exception thrown" --- Left 42 --- @ -try :: - forall exn (es :: Effects) a. - (forall e. Exception exn e -> Eff (e :& es) a) -> - -- | @Left@ if the exception was thrown, @Right@ otherwise - Eff es (Either exn a) -try f = - UnsafeMkEff $ withScopedException_ (\throw_ -> unsafeUnEff (f (UnsafeMkException throw_))) - --- | 'handle', but with the argument order swapped --- --- @ --- >>> runPureEff $ handle (pure . show) $ \\e -> do --- throw e 42 --- pure "No exception thrown" --- "42" --- @ -handle :: - forall exn (es :: Effects) a. - -- | If the exception is thrown, apply this handler - (exn -> Eff es a) -> - (forall e. Exception exn e -> Eff (e :& es) a) -> - Eff es a -handle h f = - try f >>= \case - Left e -> h e - Right a -> pure a - -catchL :: - forall exn (es :: Effects) a. - (forall e. Exception exn e -> Eff (e :& es) a) %1 -> - -- | If the exception is thrown, apply this handler - (exn %1 -> Eff es a) -> - Eff es a -catchL x y = - Unsafe.Linear.toLinear - (\(MkWrapCatch x') y' -> catch x' y') - (MkWrapCatch x) - (\e -> y e) - -newtype WrapCatch exn es a - = MkWrapCatch (forall e. Exception exn e -> Eff (e :& es) a) - -catch :: - forall exn (es :: Effects) a. - (forall e. Exception exn e -> Eff (e :& es) a) -> - -- | If the exception is thrown, apply this handler - (exn -> Eff es a) -> - Eff es a -catch f h = handle h f - --- | @bracket acquire release body@: @acquire@ a resource, perform the --- @body@ with it, and @release@ the resource even if @body@ threw an --- exception. This is essentially the same as --- @Control.Exception.'Control.Exception.bracket'@, whose --- documentation you can inspect for further details. --- --- @bracket@ has a very general type that does not require @es@ to --- contain an exception or IO effect. The reason that this is safe is: --- --- * While @bracket@ does catch exceptions, this is unobservable, --- since the exception is re-thrown; the cleanup action happens --- unconditionally; and no part of it gets access to the thrown --- exception. --- --- * 'Eff' itself is able to guarantee that any exceptions thrown --- in the body will be actually thrown before @bracket@ --- exits. This is inherited from the fact that @Eff@ is a wrapper --- around 'IO'. --- --- While it is usually the case that the cleanup action will in fact --- want to use @IO@ effects, this is not universally true, see the --- @polymorphicBracket@ example for an example. -bracket :: - -- | Acquire the resource - Eff es a -> - -- | Release the resource - (a -> Eff es ()) -> - -- | Run the body - (a -> Eff es b) -> - Eff es b -bracket before after body = - UnsafeMkEff $ - Control.Exception.bracket - (unsafeUnEff before) - (unsafeUnEff . after) - (unsafeUnEff . body) - --- | --- @ --- >>> runPureEff $ runState 10 $ \\st -> do --- n <- get st --- pure (2 * n) --- (20,10) --- @ -get :: - (e :> es) => - State s e -> - -- | The current value of the state - Eff es s -get (UnsafeMkState r) = UnsafeMkEff (readIORef r) - --- | Set the value of the state --- --- @ --- >>> runPureEff $ runState 10 $ \\st -> do --- put st 30 --- ((), 30) --- @ -put :: - (e :> es) => - State s e -> - -- | The new value of the state. The new value is forced before - -- writing it to the state. - s -> - Eff es () -put (UnsafeMkState r) s = UnsafeMkEff (writeIORef r $! s) - --- | --- @ --- >>> runPureEff $ runState 10 $ \\st -> do --- modify st (* 2) --- ((), 20) --- @ -modify :: - (e :> es) => - State s e -> - -- | Apply this function to the state. The new value of the state - -- is forced before writing it to the state. - (s -> s) -> - Eff es () -modify state f = do - s <- get state - put state (f s) - --- This is roughly how effectful does it -data MyException where - MyException :: e -> Data.Unique.Unique -> MyException - -instance Show MyException where - show _ = "" - -instance Control.Exception.Exception MyException - -withScopedException_ :: ((forall a. e -> IO a) -> IO r) -> IO (Either e r) -withScopedException_ f = do - fresh <- Data.Unique.newUnique - - flip tryJust (f (\e -> throwIO (MyException e fresh))) $ \case - MyException e tag -> - -- unsafeCoerce is very unpleasant - if tag == fresh then Just (unsafeCoerce e) else Nothing - --- | --- @ --- 'runPureEff' $ 'withStateSource' $ \\source -> do --- n <- 'newState' source 5 --- total <- newState source 0 --- --- 'withJump' $ \\done -> forever $ do --- n' <- 'Bluefin.State.get' n --- 'Bluefin.State.modify' total (+ n') --- when (n' == 0) $ 'Bluefin.Jump.jumpTo' done --- modify n (subtract 1) --- --- get total --- 15 --- @ -withStateSource :: - (forall e. StateSource e -> Eff (e :& es) a) -> - -- | ͘ - Eff es a -withStateSource f = unsafeRemoveEff (f StateSource) - --- | --- @ --- runPureEff $ 'withStateSource' $ \\source -> do --- n <- 'newState' source 5 --- total <- newState source 0 --- --- 'Bluefin.Jump.withJump' $ \\done -> forever $ do --- n' <- 'Bluefin.State.get' n --- 'Bluefin.State.modify' total (+ n') --- when (n' == 0) $ 'Bluefin.Jump.jumpTo' done --- modify n (subtract 1) --- --- get total --- 15 --- @ -newState :: - StateSource e -> - -- | The initial value for the state handle - s -> - -- | A new state handle - Eff es (State s e) -newState StateSource s = UnsafeMkEff (fmap UnsafeMkState (newIORef s)) - --- | --- @ --- >>> runPureEff $ runState 10 $ \\st -> do --- n <- get st --- pure (2 * n) --- (20,10) --- @ -runState :: - -- | Initial state - s -> - -- | Stateful computation - (forall e. State s e -> Eff (e :& es) a) -> - -- | Result and final state - Eff es (a, s) -runState s f = do - withStateSource $ \source -> do - state <- newState source s - a <- f state - s' <- get state - pure (a, s') - -yieldCoroutine :: - (e1 :> es) => - Coroutine a b e1 -> - -- | ͘ - a -> - Eff es b -yieldCoroutine (MkCoroutine f) = useImpl . f - --- | --- @ --- >>> runPureEff $ yieldToList $ \\y -> do --- yield y 1 --- yield y 2 --- yield y 100 --- ([1,2,100], ()) --- @ -yield :: - (e1 :> es) => - Stream a e1 -> - -- | Yield this value from the stream - a -> - Eff es () -yield = yieldCoroutine - -handleCoroutine :: - (a -> Eff es b) -> - (z -> Eff es r) -> - (forall e1. Coroutine a b e1 -> Eff (e1 :& es) z) -> - Eff es r -handleCoroutine update finish f = do - z <- forEach f update - finish z - --- | --- @ --- >>> runPureEff $ yieldToList $ \\y -> do --- forEach (inFoldable [0 .. 3]) $ \\i -> do --- yield y i --- yield y (i * 10) --- ([0, 0, 1, 10, 2, 20, 3, 30], ()) --- @ -forEach :: - (forall e1. Coroutine a b e1 -> Eff (e1 :& es) r) -> - -- | Apply this effectful function for each element of the coroutine - (a -> Eff es b) -> - Eff es r -forEach f h = useImplIn f (MkCoroutine h) - --- | --- @ --- >>> runPureEff $ yieldToList $ inFoldable [1, 2, 100] --- ([1, 2, 100], ()) --- @ -inFoldable :: - (Foldable t, e1 :> es) => - -- | Yield all these values from the stream - t a -> - Stream a e1 -> - Eff es () -inFoldable t = for_ t . yield - --- | Pair each element in the stream with an increasing index, --- starting from 0. --- --- @ --- >>> runPureEff $ yieldToList $ enumerate (inFoldable [\"A\", \"B\", \"C\"]) --- ([(0, \"A\"), (1, \"B\"), (2, \"C\")], ()) --- @ -enumerate :: - (e2 :> es) => - -- | ͘ - (forall e1. Stream a e1 -> Eff (e1 :& es) r) -> - Stream (Int, a) e2 -> - Eff es r -enumerate s = enumerateFrom 0 s - --- | Pair each element in the stream with an increasing index, --- starting from an inital value. --- --- @ --- >>> runPureEff $ yieldToList $ enumerateFrom1 (inFoldable [\"A\", \"B\", \"C\"]) --- ([(1, \"A\"), (2, \"B\"), (3, \"C\")], ()) --- @ -enumerateFrom :: - (e2 :> es) => - -- | Initial value - Int -> - (forall e1. Stream a e1 -> Eff (e1 :& es) r) -> - Stream (Int, a) e2 -> - Eff es r -enumerateFrom n ss st = - evalState n $ \i -> forEach (insertSecond . ss) $ \s -> do - ii <- get i - yield st (ii, s) - put i (ii + 1) - -type EarlyReturn = Exception - --- | Run an 'Eff' action with the ability to return early to this --- point. In the language of exceptions, 'withEarlyReturn' installs --- an exception handler for an exception of type @r@. --- --- @ --- >>> runPureEff $ withEarlyReturn $ \\e -> do --- for_ [1 .. 10] $ \\i -> do --- when (i >= 5) $ --- returnEarly e ("Returned early with " ++ show i) --- pure "End of loop" --- "Returned early with 5" --- @ -withEarlyReturn :: - (forall e. EarlyReturn r e -> Eff (e :& es) r) -> - -- | ͘ - Eff es r -withEarlyReturn = handle pure - --- | --- @ --- >>> runPureEff $ withEarlyReturn $ \\e -> do --- for_ [1 .. 10] $ \\i -> do --- when (i >= 5) $ --- returnEarly e ("Returned early with " ++ show i) --- pure "End of loop" --- "Returned early with 5" --- @ -returnEarly :: - (e :> es) => - EarlyReturn r e -> - -- | Return early to the handler, with this value. - r -> - Eff es a -returnEarly = throw - --- | --- @ --- >>> runPureEff $ evalState 10 $ \\st -> do --- n <- get st --- pure (2 * n) --- 20 --- @ -evalState :: - -- | Initial state - s -> - -- | Stateful computation - (forall e. State s e -> Eff (e :& es) a) -> - -- | Result - Eff es a -evalState s f = fmap fst (runState s f) - --- | --- @ --- >>> runPureEff $ withState 10 $ \\st -> do --- n <- get st --- pure (\s -> (2 * n, s)) --- (20,10) --- @ -withState :: - -- | Initial state - s -> - -- | Stateful computation - (forall st. State s st -> Eff (st :& es) (s -> a)) -> - -- | Result - Eff es a -withState s f = do - (g, s') <- runState s f - pure (g s') - -data Compound e1 e2 ss where - Compound :: - Proxy# s1 -> - Proxy# s2 -> - e1 s1 -> - e2 s2 -> - Compound e1 e2 (s1 :& s2) - -compound :: - h1 e1 -> - -- | ͘ - h2 e2 -> - Compound h1 h2 (e1 :& e2) -compound = Compound proxy# proxy# - -inComp :: forall a b c r. (a :> b) => (b :> c) => ((a :> c) => r) -> r -inComp k = case have (cmp (has @a @b) (has @b @c)) of Dict -> k - -withCompound :: - forall h1 h2 e es r. - (e :> es) => - Compound h1 h2 e -> - -- | ͘ - (forall e1 e2. (e1 :> es, e2 :> es) => h1 e1 -> h2 e2 -> Eff es r) -> - Eff es r -withCompound c f = - case c of - Compound (_ :: Proxy# st) (_ :: Proxy# st') h i -> - inComp @st @e @es (inComp @st' @e @es (f h i)) - -withC1 :: - forall e1 e2 ss es r. - (ss :> es) => - Compound e1 e2 ss -> - (forall st. (st :> es) => e1 st -> Eff es r) -> - Eff es r -withC1 c f = withCompound c (\h _ -> f h) - -withC2 :: - forall e1 e2 ss es r. - (ss :> es) => - Compound e1 e2 ss -> - (forall st. (st :> es) => e2 st -> Eff es r) -> - Eff es r -withC2 c f = withCompound c (\_ i -> f i) - -putC :: forall ss es e. (ss :> es) => Compound e (State Int) ss -> Int -> Eff es () -putC c i = withC2 c (\h -> put h i) - -getC :: forall ss es e. (ss :> es) => Compound e (State Int) ss -> Eff es Int -getC c = withC2 c (\h -> get h) - --- TODO: Make this (s1 :> es, s2 :> es), like withC -runCompound :: - e1 s1 -> - -- | ͘ - e2 s2 -> - (forall es'. Compound e1 e2 es' -> Eff (es' :& es) r) -> - Eff (s1 :& (s2 :& es)) r -runCompound e1 e2 k = assoc1Eff (k (compound e1 e2)) - --- | --- @ --- >>> runPureEff $ yieldToList $ \\y -> do --- yield y 1 --- yield y 2 --- yield y 100 --- ([1,2,100], ()) --- @ -yieldToList :: - (forall e1. Stream a e1 -> Eff (e1 :& es) r) -> - -- | Yielded elements and final result - Eff es ([a], r) -yieldToList f = do - (as, r) <- yieldToReverseList f - pure (reverse as, r) - --- | --- @ --- >>> runPureEff $ withYieldToList $ \\y -> do --- yield y 1 --- yield y 2 --- yield y 100 --- pure length --- 3 --- @ -withYieldToList :: - -- | Stream computation - (forall e. Stream a e -> Eff (e :& es) ([a] -> r)) -> - -- | Result - Eff es r -withYieldToList f = do - (l, g) <- yieldToList f - pure (g l) - --- | This is more efficient than 'yieldToList' because it gathers the --- elements into a stack in reverse order. @yieldToList@ then reverses --- that stack. --- --- @ --- >>> runPureEff $ yieldToReverseList $ \\y -> do --- yield y 1 --- yield y 2 --- yield y 100 --- ([100,2,1], ()) --- @ -yieldToReverseList :: - (forall e. Stream a e -> Eff (e :& es) r) -> - -- | Yielded elements in reverse order, and final result - Eff es ([a], r) -yieldToReverseList f = do - evalState [] $ \(s :: State lo st) -> do - r <- forEach (insertSecond . f) $ \i -> - modify s (i :) - as <- get s - pure (as, r) - -mapStream :: - (e2 :> es) => - -- | Apply this function to all elements of the input stream. - (a -> b) -> - -- | Input stream - (forall e1. Stream a e1 -> Eff (e1 :& es) r) -> - Stream b e2 -> - Eff es r -mapStream f = mapMaybe (Just . f) - -mapMaybe :: - (e2 :> es) => - -- | Yield from the output stream all of the elemnts of the input - -- stream for which this function returns @Just@ - (a -> Maybe b) -> - -- | Input stream - (forall e1. Stream a e1 -> Eff (e1 :& es) r) -> - Stream b e2 -> - Eff es r -mapMaybe f s y = forEach s $ \a -> do - case f a of - Nothing -> pure () - Just b_ -> yield y b_ - --- | Remove 'Nothing' elements from a stream. -catMaybes :: - (e2 :> es) => - -- | Input stream - (forall e1. Stream (Maybe a) e1 -> Eff (e1 :& es) r) -> - Stream a e2 -> - Eff es r -catMaybes s y = mapMaybe id s y - -type Jump = EarlyReturn () - --- | --- @ --- runPureEff $ 'withStateSource' $ \\source -> do --- n <- 'newState' source 5 --- total <- newState source 0 --- --- 'Bluefin.Jump.withJump' $ \\done -> forever $ do --- n' <- 'Bluefin.State.get' n --- 'Bluefin.State.modify' total (+ n') --- when (n' == 0) $ 'Bluefin.Jump.jumpTo' done --- modify n (subtract 1) --- --- get total --- 15 --- @ -withJump :: - (forall e. Jump e -> Eff (e :& es) ()) -> - -- | ͘ - Eff es () -withJump = withEarlyReturn - --- | --- @ --- runPureEff $ 'withStateSource' $ \\source -> do --- n <- 'newState' source 5 --- total <- newState source 0 --- --- 'Bluefin.Jump.withJump' $ \\done -> forever $ do --- n' <- 'Bluefin.State.get' n --- 'Bluefin.State.modify' total (+ n') --- when (n' == 0) $ 'Bluefin.Jump.jumpTo' done --- modify n (subtract 1) --- --- get total --- 15 --- @ -jumpTo :: - (e :> es) => - Jump e -> - -- | ͘ - Eff es a -jumpTo tag = throw tag () - -unwrap :: (e :> es) => Jump e -> Maybe a -> Eff es a -unwrap j = \case - Nothing -> jumpTo j - Just a -> pure a - --- | Handle that allows you to run 'IO' operations -data IOE (e :: Effects) = MkIOE - --- | Run an 'IO' operation in 'Eff' --- --- @ --- >>> runEff $ \\io -> do --- effIO io (putStrLn "Hello world!") --- Hello, world! --- @ -effIO :: - (e :> es) => - IOE e -> - IO a -> - -- | ͘ - Eff es a -effIO MkIOE = UnsafeMkEff - --- | Run an 'Eff' whose only unhandled effect is 'IO'. --- --- @ --- >>> runEff $ \\io -> do --- effIO io (putStrLn "Hello world!") --- Hello, world! --- @ -runEff :: - (forall e es. IOE e -> Eff (e :& es) a) -> - -- | ͘ - IO a -runEff eff = unsafeUnEff (eff MkIOE) - -unsafeProvideIO :: - (forall e. IOE e -> Eff (e :& es) a) -> - -- | ͘ - Eff es a -unsafeProvideIO eff = unsafeRemoveEff (eff MkIOE) - -connect :: - (forall e1. Coroutine a b e1 -> Eff (e1 :& es) r1) -> - (forall e2. a -> Coroutine b a e2 -> Eff (e2 :& es) r2) -> - forall e1 e2. - (e1 :> es, e2 :> es) => - Eff - es - ( Either - (r1, a -> Coroutine b a e2 -> Eff es r2) - (r2, b -> Coroutine a b e1 -> Eff es r1) - ) -connect _ _ = error "connect unimplemented, sorry" - -head' :: - forall a b r es. - (forall e. Coroutine a b e -> Eff (e :& es) r) -> - forall e. - (e :> es) => - Eff - es - ( Either - r - (a, b -> Coroutine a b e -> Eff es r) - ) -head' c = do - r <- connect c (\a _ -> pure a) @_ @es - pure $ case r of - Right r' -> Right r' - Left (l, _) -> Left l - -newtype Writer w e = Writer (Stream w e) - --- | --- @ --- >>> 'Data.Monoid.getAny' $ snd $ runPureEff $ runWriter $ \\w -> do --- -- Non-empty list (the tell event does happen) --- for_ [1 .. 10] $ \\_ -> tell w ('Data.Monoid.Any' True) --- True --- @ -runWriter :: - (Monoid w) => - -- | ͘ - (forall e. Writer w e -> Eff (e :& es) r) -> - Eff es (r, w) -runWriter f = runState mempty $ \st -> do - forEach (insertSecond . f . Writer) $ \ww -> do - modify st (<> ww) - --- | --- @ --- >>> 'Data.Monoid.getAny' $ runPureEff $ execWriter $ \\w -> do --- -- Non-empty list (the tell event does happen) --- for_ [1 .. 10] $ \\_ -> tell w ('Data.Monoid.Any' True) --- True --- @ --- --- @ --- >>> 'Data.Monoid.getAny' $ runPureEff $ execWriter $ \\w -> do --- -- Empty list (the tell event does not happen) --- for_ [] $ \\_ -> tell w ('Data.Monoid.Any' True) --- False --- @ -execWriter :: - (Monoid w) => - -- | ͘ - (forall e. Writer w e -> Eff (e :& es) r) -> - Eff es w -execWriter f = fmap snd (runWriter f) - --- | --- @ --- >>> 'Data.Monoid.getAny' $ runPureEff $ execWriter $ \\w -> do --- -- Non-empty list (the tell event does happen) --- for_ [1 .. 10] $ \\_ -> tell w ('Data.Monoid.Any' True) --- True --- @ -tell :: - (e :> es) => - Writer w e -> - -- | ͘ - w -> - Eff es () -tell (Writer y) = yield y - -newtype Reader r (e :: Effects) = MkReader r - -instance Handle (Reader r) where - mapHandle (MkReader r) = MkReader r - -runReader :: - -- | ͘ - r -> - (forall e. Reader r e -> Eff (e :& es) a) -> - Eff es a -runReader r f = unsafeRemoveEff (f (MkReader r)) - -ask :: - (e :> es) => - -- | ͘ - Reader r e -> - Eff es r -ask (MkReader r) = pure r diff --git a/bluefin-internal/src/Bluefin/Internal/Examples.hs b/bluefin-internal/src/Bluefin/Internal/Examples.hs deleted file mode 100644 index 634ea46..0000000 --- a/bluefin-internal/src/Bluefin/Internal/Examples.hs +++ /dev/null @@ -1,746 +0,0 @@ -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE LinearTypes #-} -{-# LANGUAGE QualifiedDo #-} -{-# LANGUAGE NoMonoLocalBinds #-} -{-# LANGUAGE NoMonomorphismRestriction #-} - -module Bluefin.Internal.Examples where - -import Bluefin.Internal hiding (w) -import Bluefin.Internal.Pipes - ( Producer, - runEffect, - stdinLn, - stdoutLn, - takeWhile', - (>->), - ) -import qualified Bluefin.Internal.Pipes as P -import Control.Exception (IOException) -import qualified Control.Exception -import qualified Control.Functor.Linear as L -import Control.Monad (forever, unless, when) -import Control.Monad.IO.Class (liftIO) -import Data.Foldable (for_) -import Data.Monoid (Any (Any, getAny)) -import Text.Read (readMaybe) -import Prelude hiding - ( break, - drop, - head, - read, - readFile, - return, - writeFile, - ) -import qualified Prelude - -monadIOExample :: IO () -monadIOExample = runEff $ \io -> withMonadIO io $ liftIO $ do - name <- readLn - putStrLn ("Hello " ++ name) - -monadFailExample :: Either String () -monadFailExample = runPureEff $ try $ \e -> - when ((2 :: Int) > 1) $ - withMonadFail e (fail "2 was bigger than 1") - -throwExample :: Either Int String -throwExample = runPureEff $ try $ \e -> do - _ <- throw e 42 - pure "No exception thrown" - -handleExample :: String -handleExample = runPureEff $ handle (pure . show) $ \e -> do - _ <- throw e (42 :: Int) - pure "No exception thrown" - -exampleGet :: (Int, Int) -exampleGet = runPureEff $ runState 10 $ \st -> do - n <- get st - pure (2 * n) - -examplePut :: ((), Int) -examplePut = runPureEff $ runState 10 $ \st -> do - put st 30 - -exampleModify :: ((), Int) -exampleModify = runPureEff $ runState 10 $ \st -> do - modify st (* 2) - -yieldExample :: ([Int], ()) -yieldExample = runPureEff $ yieldToList $ \y -> do - yield y 1 - yield y 2 - yield y 100 - -withYieldToListExample :: Int -withYieldToListExample = runPureEff $ withYieldToList $ \y -> do - yield y (1 :: Int) - yield y 2 - yield y 100 - pure length - -forEachExample :: ([Int], ()) -forEachExample = runPureEff $ yieldToList $ \y -> do - forEach (inFoldable [0 .. 4]) $ \i -> do - yield y i - yield y (i * 10) - -inFoldableExample :: ([Int], ()) -inFoldableExample = runPureEff $ yieldToList $ inFoldable [1, 2, 100] - -enumerateExample :: ([(Int, String)], ()) -enumerateExample = runPureEff $ yieldToList $ enumerate (inFoldable ["A", "B", "C"]) - -returnEarlyExample :: String -returnEarlyExample = runPureEff $ withEarlyReturn $ \e -> do - for_ [1 :: Int .. 10] $ \i -> do - when (i >= 5) $ - returnEarly e ("Returned early with " ++ show i) - pure "End of loop" - -effIOExample :: IO () -effIOExample = runEff $ \io -> do - effIO io (putStrLn "Hello world!") - -example1_ :: (Int, Int) -example1_ = - let example1 :: Int -> Int - example1 n = runPureEff $ evalState n $ \st -> do - n' <- get st - when (n' < 10) $ - put st (n' + 10) - get st - in (example1 5, example1 12) - -example2_ :: ((Int, Int), (Int, Int)) -example2_ = - let example2 :: (Int, Int) -> (Int, Int) - example2 (m, n) = runPureEff $ - evalState m $ \sm -> do - evalState n $ \sn -> do - do - n' <- get sn - m' <- get sm - - if n' < m' - then put sn (n' + 10) - else put sm (m' + 10) - - n' <- get sn - m' <- get sm - - pure (n', m') - in (example2 (5, 10), example2 (12, 5)) - -example3' :: Int -> Either String Int -example3' n = runPureEff $ - try $ \ex -> do - evalState 0 $ \total -> do - for_ [1 .. n] $ \i -> do - soFar <- get total - when (soFar > 20) $ do - throw ex ("Became too big: " ++ show soFar) - put total (soFar + i) - - get total - -{- - --- Count non-empty lines from stdin, and print a friendly message, --- until we see "STOP". -example3_ :: IO () -example3_ = runEff $ \io -> do - let getLineUntilStop y = withJump $ \stop -> forever $ do - line <- effIO io getLine - when (line == "STOP") $ - jumpTo stop - yield y line - - nonEmptyLines = - mapMaybe - ( \case - "" -> Nothing - line -> Just line - ) - getLineUntilStop - - enumeratedLines = enumerateFrom 1 nonEmptyLines - - formattedLines = - mapStream - (\(i, line) -> show i ++ ". Hello! You said " ++ line) - enumeratedLines - - forEach formattedLines $ \line -> effIO io (putStrLn line) - --} - --- Count the number of (strictly) positives and (strictly) negatives --- in a list, unless we see a zero, in which case we bail with an --- error message. -countPositivesNegatives :: [Int] -> String -countPositivesNegatives is = runPureEff $ - evalState (0 :: Int) $ \positives -> do - r <- try $ \ex -> - evalState (0 :: Int) $ \negatives -> do - for_ is $ \i -> do - case compare i 0 of - GT -> modify positives (+ 1) - EQ -> throw ex () - LT -> modify negatives (+ 1) - - p <- get positives - n <- get negatives - - pure $ - "Positives: " - ++ show p - ++ ", negatives " - ++ show n - - case r of - Right r' -> pure r' - Left () -> do - p <- get positives - pure $ - "We saw a zero, but before that there were " - ++ show p - ++ " positives" - --- How to make compound effects - -type MyHandle = Compound (State Int) (Exception String) - -myInc :: (e :> es) => MyHandle e -> Eff es () -myInc h = withCompound h (\s _ -> modify s (+ 1)) - -myBail :: (e :> es) => MyHandle e -> Eff es r -myBail h = withCompound h $ \s e -> do - i <- get s - throw e ("Current state was: " ++ show i) - -runMyHandle :: - (forall e. MyHandle e -> Eff (e :& es) a) -> - Eff es (Either String (a, Int)) -runMyHandle f = - try $ \e -> do - runState 0 $ \s -> do - runCompound s e f - -compoundExample :: Either String (a, Int) -compoundExample = runPureEff $ runMyHandle $ \h -> do - myInc h - myInc h - myBail h - -countExample :: IO () -countExample = runEff $ \io -> do - evalState @Int 0 $ \sn -> do - withJump $ \break -> forever $ do - n <- get sn - when (n >= 10) (jumpTo break) - effIO io (print n) - modify sn (+ 1) - -writerExample1 :: Bool -writerExample1 = getAny $ runPureEff $ execWriter $ \w -> do - for_ [] $ \_ -> tell w (Any True) - -writerExample2 :: Bool -writerExample2 = getAny $ runPureEff $ execWriter $ \w -> do - for_ [1 .. 10 :: Int] $ \_ -> tell w (Any True) - -while :: Eff es Bool -> Eff es a -> Eff es () -while condM body = - withJump $ \break_ -> do - forever $ do - cond <- insertFirst condM - unless cond (jumpTo break_) - insertFirst body - -stateSourceExample :: Int -stateSourceExample = runPureEff $ withStateSource $ \source -> do - n <- newState source 5 - total <- newState source 0 - - withJump $ \done -> forever $ do - n' <- get n - modify total (+ n') - when (n' == 0) $ jumpTo done - modify n (subtract 1) - - get total - -incrementReadLine :: - (e1 :> es, e2 :> es, e3 :> es) => - State Int e1 -> - Exception String e2 -> - IOE e3 -> - Eff es () -incrementReadLine state exception io = do - withJump $ \break -> forever $ do - line <- effIO io getLine - i <- case readMaybe line of - Nothing -> - throw exception ("Couldn't read: " ++ line) - Just i -> - pure i - - when (i == 0) $ - jumpTo break - - modify state (+ i) - -runIncrementReadLine :: IO (Either String Int) -runIncrementReadLine = runEff $ \io -> do - try $ \exception -> do - ((), r) <- runState 0 $ \state -> do - incrementReadLine state exception io - pure r - --- Counter 1 - -newtype Counter1 e = MkCounter1 (State Int e) - -incCounter1 :: (e :> es) => Counter1 e -> Eff es () -incCounter1 (MkCounter1 st) = modify st (+ 1) - -runCounter1 :: - (forall e. Counter1 e -> Eff (e :& es) r) -> - Eff es Int -runCounter1 k = - evalState 0 $ \st -> do - _ <- k (MkCounter1 st) - get st - -exampleCounter1 :: Int -exampleCounter1 = runPureEff $ runCounter1 $ \c -> do - incCounter1 c - incCounter1 c - incCounter1 c - --- > exampeleCounter1 --- 3 - --- Counter 2 - -data Counter2 e1 e2 = MkCounter2 (State Int e1) (Exception () e2) - -incCounter2 :: (e1 :> es, e2 :> es) => Counter2 e1 e2 -> Eff es () -incCounter2 (MkCounter2 st ex) = do - count <- get st - when (count >= 10) $ - throw ex () - put st (count + 1) - -runCounter2 :: - (forall e1 e2. Counter2 e1 e2 -> Eff (e2 :& e1 :& es) r) -> - Eff es Int -runCounter2 k = - evalState 0 $ \st -> do - _ <- try $ \ex -> do - k (MkCounter2 st ex) - get st - -exampleCounter2 :: Int -exampleCounter2 = runPureEff $ runCounter2 $ \c -> - forever $ - incCounter2 c - --- > exampleCounter2 --- 10 - --- Counter 3 - -data Counter3 e = MkCounter3 (State Int e) (Exception () e) - -incCounter3 :: (e :> es) => Counter3 e -> Eff es () -incCounter3 (MkCounter3 st ex) = do - count <- get st - when (count >= 10) $ - throw ex () - put st (count + 1) - -runCounter3 :: - (forall e. Counter3 e -> Eff (e :& es) r) -> - Eff es Int -runCounter3 k = - evalState 0 $ \st -> do - _ <- try $ \ex -> do - useImplIn k (MkCounter3 (mapHandle st) (mapHandle ex)) - get st - -exampleCounter3 :: Int -exampleCounter3 = runPureEff $ runCounter3 $ \c -> - forever $ - incCounter3 c - --- > exampleCounter3 --- 10 - --- Counter 4 - -data Counter4 e - = MkCounter4 (State Int e) (Exception () e) (Stream String e) - -incCounter4 :: (e :> es) => Counter4 e -> Eff es () -incCounter4 (MkCounter4 st ex y) = do - count <- get st - - when (even count) $ - yield y "Count was even" - - when (count >= 10) $ - throw ex () - - put st (count + 1) - -getCounter4 :: (e :> es) => Counter4 e -> String -> Eff es Int -getCounter4 (MkCounter4 st _ y) msg = do - yield y msg - get st - -runCounter4 :: - (e1 :> es) => - Stream String e1 -> - (forall e. Counter4 e -> Eff (e :& es) r) -> - Eff es Int -runCounter4 y k = - evalState 0 $ \st -> do - _ <- try $ \ex -> do - useImplIn k (MkCounter4 (mapHandle st) (mapHandle ex) (mapHandle y)) - get st - -exampleCounter4 :: ([String], Int) -exampleCounter4 = runPureEff $ yieldToList $ \y -> do - runCounter4 y $ \c -> do - incCounter4 c - incCounter4 c - n <- getCounter4 c "I'm getting the counter" - when (n == 2) $ - yield y "n was 2, as expected" - --- > exampleCounter4 --- (["Count was even","I'm getting the counter","n was 2, as expected"],2) - --- Counter 5 - -data Counter5 e = MkCounter5 - { incCounter5Impl :: Eff e (), - getCounter5Impl :: String -> Eff e Int - } - -incCounter5 :: (e :> es) => Counter5 e -> Eff es () -incCounter5 e = useImpl (incCounter5Impl e) - -getCounter5 :: (e :> es) => Counter5 e -> String -> Eff es Int -getCounter5 e msg = useImpl (getCounter5Impl e msg) - -runCounter5 :: - (e1 :> es) => - Stream String e1 -> - (forall e. Counter5 e -> Eff (e :& es) r) -> - Eff es Int -runCounter5 y k = - evalState 0 $ \st -> do - _ <- try $ \ex -> do - useImplIn - k - ( MkCounter5 - { incCounter5Impl = do - count <- get st - - when (even count) $ - yield y "Count was even" - - when (count >= 10) $ - throw ex () - - put st (count + 1), - getCounter5Impl = \msg -> do - yield y msg - get st - } - ) - get st - -exampleCounter5 :: ([String], Int) -exampleCounter5 = runPureEff $ yieldToList $ \y -> do - runCounter5 y $ \c -> do - incCounter5 c - incCounter5 c - n <- getCounter5 c "I'm getting the counter" - when (n == 2) $ - yield y "n was 2, as expected" - --- > exampleCounter5 --- (["Count was even","I'm getting the counter","n was 2, as expected"],2) - --- Counter 6 - -data Counter6 e = MkCounter6 - { incCounter6Impl :: Eff e (), - counter6State :: State Int e, - counter6Stream :: Stream String e - } - -incCounter6 :: (e :> es) => Counter6 e -> Eff es () -incCounter6 e = useImpl (incCounter6Impl e) - -getCounter6 :: (e :> es) => Counter6 e -> String -> Eff es Int -getCounter6 (MkCounter6 _ st y) msg = do - yield y msg - get st - -runCounter6 :: - (e1 :> es) => - Stream String e1 -> - (forall e. Counter6 e -> Eff (e :& es) r) -> - Eff es Int -runCounter6 y k = - evalState 0 $ \st -> do - _ <- try $ \ex -> do - useImplIn - k - ( MkCounter6 - { incCounter6Impl = do - count <- get st - - when (even count) $ - yield y "Count was even" - - when (count >= 10) $ - throw ex () - - put st (count + 1), - counter6State = mapHandle st, - counter6Stream = mapHandle y - } - ) - get st - -exampleCounter6 :: ([String], Int) -exampleCounter6 = runPureEff $ yieldToList $ \y -> do - runCounter6 y $ \c -> do - incCounter6 c - incCounter6 c - n <- getCounter6 c "I'm getting the counter" - when (n == 2) $ - yield y "n was 2, as expected" - --- > exampleCounter6 --- (["Count was even","I'm getting the counter","n was 2, as expected"],2) - --- FileSystem - -data FileSystem es = MkFileSystem - { readFileImpl :: FilePath -> Eff es String, - writeFileImpl :: FilePath -> String -> Eff es () - } - -readFile :: (e :> es) => FileSystem e -> FilePath -> Eff es String -readFile fs filepath = useImpl (readFileImpl fs filepath) - -writeFile :: (e :> es) => FileSystem e -> FilePath -> String -> Eff es () -writeFile fs filepath contents = useImpl (writeFileImpl fs filepath contents) - -runFileSystemPure :: - (e1 :> es) => - Exception String e1 -> - [(FilePath, String)] -> - (forall e2. FileSystem e2 -> Eff (e2 :& es) r) -> - Eff es r -runFileSystemPure ex fs0 k = - evalState fs0 $ \fs -> - useImplIn - k - MkFileSystem - { readFileImpl = \path -> do - fs' <- get fs - case lookup path fs' of - Nothing -> - throw ex ("File not found: " <> path) - Just s -> pure s, - writeFileImpl = \path contents -> - modify fs ((path, contents) :) - } - -runFileSystemIO :: - forall e1 e2 es r. - (e1 :> es, e2 :> es) => - Exception String e1 -> - IOE e2 -> - (forall e. FileSystem e -> Eff (e :& es) r) -> - Eff es r -runFileSystemIO ex io k = - useImplIn - k - MkFileSystem - { readFileImpl = - adapt . Prelude.readFile, - writeFileImpl = - \path -> adapt . Prelude.writeFile path - } - where - adapt :: (e1 :> ess, e2 :> ess) => IO a -> Eff ess a - adapt m = - effIO io (Control.Exception.try @IOException m) >>= \case - Left e -> throw ex (show e) - Right r -> pure r - -action :: (e :> es) => FileSystem e -> Eff es String -action fs = do - file <- readFile fs "/dev/null" - when (length file == 0) $ do - writeFile fs "/tmp/bluefin" "Hello!\n" - readFile fs "/tmp/doesn't exist" - -exampleRunFileSystemPure :: Either String String -exampleRunFileSystemPure = runPureEff $ try $ \ex -> - runFileSystemPure ex [("/dev/null", "")] action - --- > exampleRunFileSystemPure --- Left "File not found: /tmp/doesn't exist" - -exampleRunFileSystemIO :: IO (Either String String) -exampleRunFileSystemIO = runEff $ \io -> try $ \ex -> - runFileSystemIO ex io action - --- > exampleRunFileSystemIO --- Left "/tmp/doesn't exist: openFile: does not exist (No such file or directory)" --- \$ cat /tmp/bluefin --- Hello! - --- instance Handle example - -data Application e = MkApplication - { queryDatabase :: String -> Int -> Eff e [String], - applicationState :: State (Int, Bool) e, - logger :: Stream String e - } - -instance Handle Application where - mapHandle - MkApplication - { queryDatabase = q, - applicationState = a, - logger = l - } = - MkApplication - { queryDatabase = (fmap . fmap) useImpl q, - applicationState = mapHandle a, - logger = mapHandle l - } - --- This example shows a case where we can use @bracket@ polymorphically --- in order to perform correct cleanup if @es@ is instantiated to a --- set of effects that includes exceptions. -polymorphicBracket :: - (st :> es) => - State (Integer, Bool) st -> - Eff es () -> - Eff es () -polymorphicBracket st act = - bracket - (pure ()) - -- Always set the boolean indicating that we have terminated - (\_ -> modify st (\(c, _) -> (c, True))) - -- Perform the given effectful action, then increment the counter - (\_ -> do act; modify st (\(c, b_) -> ((c + 1), b_))) - --- Results in (1, True) -polymorphicBracketExample1 :: (Integer, Bool) -polymorphicBracketExample1 = - runPureEff $ do - (_res, st) <- runState (0, False) $ \st -> polymorphicBracket st (pure ()) - pure st - --- Results in (0, True) -polymorphicBracketExample2 :: (Integer, Bool) -polymorphicBracketExample2 = - runPureEff $ do - (_res, st) <- runState (0, False) $ \st -> try $ \e -> polymorphicBracket st (throw e (42 :: Int)) - pure st - -pipesExample1 :: IO () -pipesExample1 = runEff $ \io -> runEffect (count >-> P.print io) - where - count :: (e :> es) => Producer Int e -> Eff es () - count p = for_ [1 .. 5] $ \i -> P.yield p i - -pipesExample2 :: IO String -pipesExample2 = runEff $ \io -> runEffect $ do - stdinLn io >-> takeWhile' (/= "quit") >-> stdoutLn io - --- Acquiring resource --- 1 --- 2 --- 3 --- 4 --- 5 --- Releasing resource --- Finishing -promptCoroutine :: IO () -promptCoroutine = runEff $ \io -> do - -- receiveStream connects a consumer to a producer - receiveStream - -- Like a pipes Consumer. Prints the first five elements it - -- receives. - ( \r -> for_ [1 :: Int .. 5] $ \_ -> do - v <- yieldCoroutine r () - effIO io (print v) - ) - -- Like a pipes Producer. Yields successive integers indefinitely. - -- Unlike in pipes, we can simply use Bluefin's standard bracket - -- for prompt release of a resource - ( \y -> - bracket - (effIO io (putStrLn "Acquiring resource")) - (\_ -> effIO io (putStrLn "Releasing resource")) - (\_ -> for_ [1 :: Int ..] $ \i -> yield y i) - ) - effIO io (putStrLn "Finishing") - -linearlyExample :: IO () -linearlyExample = runEff $ \io -> - forEach - ( \out -> do - linearly - (\() y -> for_ ['A' .. 'H'] $ \i -> yield y i) - \l1 -> - linearly - (\() y -> for_ [1 :: Int .. 3] $ \i -> yield y i) - \l2 -> L.do - alternate out l1 l2 - ) - (\s -> effIO io (putStrLn s)) - -alternate :: - (e1 :> es, e2 :> es, e3 :> es, Show a1, Show a2) => - Stream String e3 -> - Linearly () a1 () e1 %1 -> - Linearly () a2 () e2 %1 -> - Eff es () -alternate y l1 l2 = - yieldLinearly l1 () L.>>= \case - Right (Ur r) -> L.do - yield y ("done: " <> show r) - yieldAll y l2 - Left (Ur s, l1') -> L.do - yield y ("got: " <> show s) - alternate y l2 l1' - -yieldAll :: - (e1 :> es, e2 :> es, Show a) => - Stream String e1 -> - Linearly () a () e2 %1 -> - Eff es () -yieldAll y l = - yieldLinearly l () L.>>= \case - Right (Ur r) -> L.do - yield y ("done: " <> show r) - Left (Ur s, l1) -> L.do - yield y ("got: " <> show s) - yieldAll y l1 diff --git a/bluefin-internal/src/Bluefin/Internal/Pipes.hs b/bluefin-internal/src/Bluefin/Internal/Pipes.hs deleted file mode 100644 index d7f17b5..0000000 --- a/bluefin-internal/src/Bluefin/Internal/Pipes.hs +++ /dev/null @@ -1,267 +0,0 @@ -module Bluefin.Internal.Pipes where - -import Bluefin.Internal hiding (yield) -import qualified Bluefin.Internal -import Control.Monad (forever) -import Data.Foldable (for_) -import Data.Void (Void, absurd) -import Prelude hiding (break, print, takeWhile) -import qualified Prelude - -data Proxy a' a b' b e = MkProxy (Coroutine a' a e) (Coroutine b b' e) - -type Pipe a = Proxy () a () - -type Producer = Proxy Void () () - -type Consumer a = Pipe a Void - -type Effect = Producer Void - -infixl 7 >-> - -(>->) :: - (e1 :> es) => - (forall e. Proxy a' a () b e -> Eff (e :& es) r) -> - (forall e. Proxy () b c' c e -> Eff (e :& es) r) -> - Proxy a' a c' c e1 -> - -- | ͘ - Eff es r -(>->) k1 k2 (MkProxy c1 c2) = - receiveStream - (\c -> useImplIn k2 (MkProxy (mapHandle c) (mapHandle c2))) - (\s -> useImplIn k1 (MkProxy (mapHandle c1) (mapHandle s))) - -infixr 7 <-< - -(<-<) :: - (e1 :> es) => - (forall e. Proxy () b c' c e -> Eff (e :& es) r) -> - (forall e. Proxy a' a () b e -> Eff (e :& es) r) -> - Proxy a' a c' c e1 -> - -- | ͘ - Eff es r -k1 <-< k2 = k2 >-> k1 - -for :: - (e1 :> es) => - (forall e. Proxy x' x b' b e -> Eff (e :& es) a') -> - (b -> forall e. Proxy x' x c' c e -> Eff (e :& es) b') -> - Proxy x' x c' c e1 -> - -- | ͘ - Eff es a' -for k1 k2 (MkProxy c1 c2) = - forEach (\bk -> useImplIn k1 (MkProxy (mapHandle c1) (mapHandle bk))) $ \b_ -> - useImplIn (k2 b_) (MkProxy (mapHandle c1) (mapHandle c2)) - -infixr 4 ~> - -(~>) :: - (e1 :> es) => - (a -> forall e. Proxy x' x b' b e -> Eff (e :& es) a') -> - (b -> forall e. Proxy x' x c' c e -> Eff (e :& es) b') -> - a -> - Proxy x' x c' c e1 -> - -- | ͘ - Eff es a' -(k1 ~> k2) a = for (k1 a) k2 - -infixl 4 <~ - -(<~) :: - (e1 :> es) => - (b -> forall e. Proxy x' x c' c e -> Eff (e :& es) b') -> - (a -> forall e. Proxy x' x b' b e -> Eff (e :& es) a') -> - a -> - Proxy x' x c' c e1 -> - -- | ͘ - Eff es a' -k2 <~ k1 = k1 ~> k2 - -reverseProxy :: Proxy a' a b' b e -> Proxy b b' a a' e -reverseProxy (MkProxy c1 c2) = MkProxy c2 c1 - -infixl 5 >~ - -(>~) :: - (e1 :> es) => - (forall e. Proxy a' a y' y e -> Eff (e :& es) b) -> - (forall e. Proxy () b y' y e -> Eff (e :& es) c) -> - Proxy a' a y' y e1 -> - -- | ͘ - Eff es c -(>~) k1 k2 p = - for - ( \p1 -> - k2 (reverseProxy p1) - ) - (\() p1 -> k1 (reverseProxy p1)) - (reverseProxy p) - -infixr 5 ~< - -(~<) :: - (e1 :> es) => - (forall e. Proxy () b y' y e -> Eff (e :& es) c) -> - (forall e. Proxy a' a y' y e -> Eff (e :& es) b) -> - Proxy a' a y' y e1 -> - -- | ͘ - Eff es c -(~<) k1 k2 = (>~) k2 k1 - -cat :: Pipe a a e -> Eff (e :& es) r -cat (MkProxy c1 c2) = forever $ do - a <- yieldCoroutine c1 () - yieldCoroutine c2 a - -runEffect :: - (forall e. Effect e -> Eff (e :& es) r) -> - -- | ͘ - Eff es r -runEffect k = - forEach - ( \c1 -> - forEach - ( \c2 -> - useImplIn - k - (MkProxy (mapHandle c1) (mapHandle c2)) - ) - absurd - ) - absurd - -yield :: - (e :> es) => - Proxy x1 x () a e -> - a -> - -- | ͘ - Eff es () -yield (MkProxy _ c) = Bluefin.Internal.yield c - -await :: (e :> es) => Proxy () a y' y e -> Eff es a -await (MkProxy c _) = yieldCoroutine c () - --- | @pipe@'s 'next' doesn't exist in Bluefin -next :: () -next = () - -each :: - (Foldable f) => - f a -> - Proxy x' x () a e -> - -- | ͘ - Eff (e :& es) () -each f p = for_ f (yield p) - -repeatM :: - (e :> es) => - Eff es a -> - Proxy x' x () a e -> - -- | ͘ - Eff es r -repeatM e p = forever $ do - a <- e - yield p a - -replicateM :: - (e :> es) => - Int -> - Eff es a -> - Proxy x' x () a e -> - -- | ͘ - Eff es () -replicateM n e p = for_ [0 .. n] $ \_ -> do - a <- e - yield p a - -print :: - (e2 :> es, e1 :> es, Show a) => - IOE e1 -> - Consumer a e2 -> - -- | ͘ - Eff es r -print io p = forever $ do - a <- await p - effIO io (Prelude.print a) - -unfoldr :: - (e :> es) => - (s -> Eff es (Either r (a, s))) -> - s -> - Proxy x1 x () a e -> - -- | ͘ - Eff es r -unfoldr next_ sInit p = - withEarlyReturn $ \break -> evalState sInit $ \ss -> forever $ do - s <- get ss - useImpl (next_ s) >>= \case - Left r -> returnEarly break r - Right (a, s') -> do - put ss s' - yield p a - -mapM_ :: - (e :> es) => - (a -> Eff es ()) -> - Proxy () a b b' e -> - -- | ͘ - Eff es r -mapM_ f = for cat (\a _ -> useImpl (f a)) - -drain :: - (e :> es) => - Proxy () b c' c e -> - -- | ͘ - Eff es r -drain = for cat (\_ _ -> pure ()) - -map :: - (e :> es) => - (a -> b) -> - Pipe a b e -> - -- | ͘ - Eff es r -map f = for cat (\a p1 -> yield p1 (f a)) - -mapM :: - (e :> es) => - (a -> Eff es b) -> - Pipe a b e -> - -- | ͘ - Eff es r -mapM f = for cat $ \a p -> do - b_ <- useImpl (f a) - yield p b_ - -takeWhile' :: - (e :> es) => - (r -> Bool) -> - Pipe r r e -> - -- | ͘ - Eff es r -takeWhile' predicate p = withEarlyReturn $ \early -> forever $ do - a <- await p - if predicate a - then yield p a - else returnEarly early a - -stdinLn :: - (e1 :> es, e2 :> es) => - IOE e1 -> - Producer String e2 -> - -- | ͘ - Eff es r -stdinLn io c = forever $ do - line <- effIO io getLine - yield c line - -stdoutLn :: - (e1 :> es, e2 :> es) => - IOE e1 -> - Consumer String e2 -> - -- | ͘ - Eff es r -stdoutLn io c = forever $ do - line <- await c - effIO io (putStrLn line)