packages feed

bluefin-internal 0.6.0.0 → 0.7.0.0

raw patch · 6 files changed

+173/−63 lines, 6 filesdep +vaultPVP ok

version bump matches the API change (PVP)

Dependencies added: vault

API changes (from Hackage documentation)

+ Bluefin.Internal: type Env = IORef Vault
+ Bluefin.Internal: withClonedEnv :: forall (es :: Effects) r. Eff es r -> Eff es r
+ Bluefin.Internal.Vault: MkKey :: Key (Any :: Type) -> Key a
+ Bluefin.Internal.Vault: adjust :: (a -> a) -> Key a -> Vault -> Vault
+ Bluefin.Internal.Vault: empty :: Vault
+ Bluefin.Internal.Vault: fromMine :: Key a -> Key a
+ Bluefin.Internal.Vault: insert :: Key a -> a -> Vault -> Vault
+ Bluefin.Internal.Vault: lookup :: Key a -> Vault -> Maybe a
+ Bluefin.Internal.Vault: newKey :: IO (Key a)
+ Bluefin.Internal.Vault: newtype Key a
+ Bluefin.Internal.Vault: toMine :: Key a -> Key a
+ Bluefin.Internal.Vault: type Vault = Vault RealWorld
- Bluefin.Internal: MkReader :: State r e -> Reader r (e :: Effects)
+ Bluefin.Internal: MkReader :: Key r -> Reader r (e :: k)
- Bluefin.Internal: UnsafeMkEff :: IO a -> Eff (es :: Effects) a
+ Bluefin.Internal: UnsafeMkEff :: (Env -> IO a) -> Eff (es :: Effects) a
- Bluefin.Internal: UnsafeMkHandleReader :: State (h e) e -> HandleReader (h :: Effects -> Type) (e :: Effects)
+ Bluefin.Internal: UnsafeMkHandleReader :: Reader (h e) e -> HandleReader (h :: k -> Type) (e :: k)
- Bluefin.Internal: [unsafeUnEff] :: Eff (es :: Effects) a -> IO a
+ Bluefin.Internal: [unsafeUnEff] :: Eff (es :: Effects) a -> Env -> IO a
- Bluefin.Internal: newtype HandleReader (h :: Effects -> Type) (e :: Effects)
+ Bluefin.Internal: newtype HandleReader (h :: k -> Type) (e :: k)
- Bluefin.Internal: newtype Reader r (e :: Effects)
+ Bluefin.Internal: newtype Reader r (e :: k)
- Bluefin.Internal: type Ask = Reader
+ Bluefin.Internal: type Ask = Reader :: Type -> k -> Type
- Bluefin.Internal: type AskCapability = HandleReader
+ Bluefin.Internal: type AskCapability = HandleReader :: k -> Type -> k -> Type
- Bluefin.Internal.CloneableHandle: hcReader :: forall r (e :: Effects). HandleCloner (Reader r) (Reader r) e
+ Bluefin.Internal.CloneableHandle: hcReader :: forall r (e :: Effects). HandleCloner (Reader r :: Effects -> Type) (Reader r :: Effects -> Type) e

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# 0.7.0.0++* Fix `Reader` bug that caused incorrect scoping in+  `awaitYield`/`connectRequests`/`streamConsume`/`connectCoroutines`++  <https://github.com/tomjaguarpaw/bluefin/issues/98>+ # 0.6.0.0  * Changed type of `runEff` to match `runEff_`
bluefin-internal.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               bluefin-internal-version:            0.6.0.0+version:            0.7.0.0 license:            MIT license-file:       LICENSE author:             Tom Ellis@@ -82,7 +82,8 @@       primitive >= 0.8 && < 0.10,       transformers < 0.7,       transformers-base < 0.5,-      monad-control < 1.1+      monad-control < 1.1,+      vault >= 0.3     exposed-modules:       Bluefin.Internal,       Bluefin.Internal.CloneableHandle,@@ -97,7 +98,8 @@       Bluefin.Internal.OneWayCoercible,       Bluefin.Internal.Pipes,       Bluefin.Internal.Prim,-      Bluefin.Internal.System.IO+      Bluefin.Internal.System.IO,+      Bluefin.Internal.Vault  test-suite bluefin-test     import:           defaults
src/Bluefin/Internal.hs view
@@ -16,11 +16,16 @@ import Bluefin.Internal.OneWayCoercible   ( OneWayCoercible (oneWayCoercibleImpl),     OneWayCoercibleD,+    OneWayCoercion,     gOneWayCoercible,     oneWayCoerce,     oneWayCoercible,+    oneWayCoercion,+    unsafeCoercionOfOneWayCoercion,     unsafeOneWayCoercible,   )+import Bluefin.Internal.Vault (Vault)+import Bluefin.Internal.Vault qualified as Vault import Control.Concurrent.Async qualified as Async import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar) import Control.Exception qualified@@ -30,10 +35,11 @@ import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO) import Control.Monad.Trans.Control (MonadBaseControl, StM, liftBaseWith, restoreM)+import Control.Monad.Trans.Reader (ReaderT) import Control.Monad.Trans.Reader qualified as Reader import Data.Coerce (coerce) import Data.Foldable (for_)-import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.IORef (IORef, modifyIORef, newIORef, readIORef, writeIORef) import Data.Kind (Type) import Data.Proxy (Proxy (Proxy)) import Data.Type.Coercion (Coercion (Coercion))@@ -55,14 +61,16 @@  type (:&) = Union -newtype Eff (es :: Effects) a = UnsafeMkEff {unsafeUnEff :: IO a}+type Env = IORef Vault++newtype Eff (es :: Effects) a = UnsafeMkEff {unsafeUnEff :: Env -> IO a}   deriving stock (Functor)-  deriving newtype (Applicative, Monad, MonadFix)+  deriving (Applicative, Monad, MonadFix) via ReaderT Env IO  type role Eff nominal representational  instance (e <: es) => OneWayCoercible (Eff e) (Eff es) where-  oneWayCoercibleImpl = oneWayCoercible+  oneWayCoercibleImpl = unsafeOneWayCoercible  instance (e <: es) => OneWayCoercible (Eff e r) (Eff es r) where   oneWayCoercibleImpl = oneWayCoercible@@ -89,7 +97,7 @@   ((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 io)))+withEffToIO k io = UnsafeMkEff (\env -> k (\f -> unsafeUnEff (f io) env))  withEffToIO' ::   (e2 <: es) =>@@ -142,15 +150,24 @@   Eff es a race x y io = do   r <- withEffToIO' io $ \toIO ->-    Async.race (toIO x) (toIO y)+    Async.race (toIO (withClonedEnv . x)) (toIO (withClonedEnv . y))    pure $ case r of     Left a -> a     Right a -> a +withClonedEnv :: Eff es r -> Eff es r+withClonedEnv m = UnsafeMkEff $ \vault -> do+  vault' <- cloneIORef vault+  case m of UnsafeMkEff m' -> m' vault'+  where+    cloneIORef ref = do+      orig <- readIORef ref+      newIORef orig+ -- | 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 awaiting an @a@).+-- exchanging @a@s and @b@s. When the former yields its first @a@ it+-- starts the latter (which is awaiting an @a@). connectCoroutines ::   forall es a b r.   (forall e. Coroutine a b e -> Eff (e :& es) r) ->@@ -1491,7 +1508,7 @@   IO a ->   -- | ͘   Eff es a-effIO MkIOE = UnsafeMkEff+effIO MkIOE = UnsafeMkEff . const  -- | Run an 'Eff' whose only unhandled effect is 'IO'. --@@ -1520,7 +1537,9 @@   (forall e. IOE e -> Eff e a) ->   -- | ͘   IO a-runEff_ eff = unsafeUnEff (eff MkIOE)+runEff_ eff = do+  emptyEnv <- newIORef Vault.empty+  unsafeUnEff (eff MkIOE) emptyEnv  unsafeProvideIO ::   (forall e. IOE e -> Eff (e :& es) a) ->@@ -1616,18 +1635,25 @@   Eff es () tell (Writer y) = yield y -newtype Reader r e = MkReader (State r e)-  deriving newtype (Handle)+newtype Reader r e = MkReader (Vault.Key r)+  deriving (Handle) via OneWayCoercibleHandle (Reader r)  instance (e <: es) => OneWayCoercible (Reader r e) (Reader r es) where   oneWayCoercibleImpl = oneWayCoercible +type role Reader representational nominal+ runReader ::   -- | Initial value for @Reader@.   r ->   (forall e. Reader r e -> Eff (e :& es) a) ->   Eff es a-runReader r f = evalState r (f . MkReader)+runReader r f = do+  k <- UnsafeMkEff $ \vault -> do+    k <- Vault.newKey+    modifyIORef vault (\v -> Vault.insert k r v)+    pure k+  makeOp (f (MkReader k))  -- | Read the value.  Note that @ask@ has the property that these two -- operations are always equivalent:@@ -1649,7 +1675,22 @@   -- | ͘   Reader r e ->   Eff es r-ask (MkReader st) = get st+ask (MkReader k) = UnsafeMkEff $ \vault -> do+  v <- readIORef vault+  case Vault.lookup k v of+    Nothing -> error msg+    Just ref -> pure ref+  where+    msg =+      unlines+        [ "ask called on out of scope reference",+          unwords+            [ "If you haven't subverted Bluefin's type system",+              "then this is a Bluefin bug.",+              "Please report it at",+              "https://github.com/tomjaguarpaw/bluefin/issues/new"+            ]+        ]  -- | Read the value modified by a function asks ::@@ -1658,7 +1699,7 @@   -- | Read the value modified by this function   (r -> a) ->   Eff es a-asks (MkReader st) f = fmap f (get st)+asks r f = fmap f (ask r)  -- | Locally override the value in the @Reader@. It will be restored -- when the @local@ block ends.@@ -1670,14 +1711,14 @@   -- | Body   Eff es a ->   Eff es a-local (MkReader st) f k = do-  orig <- get st-  bracket-    (put st (f orig))-    (\() -> put st orig)-    (\() -> k)+local (MkReader key) f k = UnsafeMkEff $ \env@vault -> do+  orig <- readIORef vault+  Control.Exception.bracket+    (writeIORef vault (Vault.adjust f key orig))+    (\() -> writeIORef vault orig)+    (\() -> case k of UnsafeMkEff m -> m env) -newtype HandleReader h e = UnsafeMkHandleReader (State (h e) e)+newtype HandleReader h e = UnsafeMkHandleReader (Reader (h e) e)   deriving (Handle) via OneWayCoercibleHandle (HandleReader h)  -- In general this is really tremendously unsafe because we could take@@ -1695,8 +1736,12 @@   HandleReader h es mapHandleReader = case coerceH of Coercion -> coerce   where+    oneWayCoerceH :: OneWayCoercion (h e) (h es)+    oneWayCoerceH = case handleDictOfHandleD (handleImpl @h) of+      MkHandleDict -> oneWayCoercion+     coerceH :: Coercion (h e) (h es)-    coerceH = unsafeCoerce (Coercion :: Coercion (h e) (h e))+    coerceH = unsafeCoercionOfOneWayCoercion oneWayCoerceH  localHandle ::   (e <: es, Handle h) =>@@ -1705,20 +1750,16 @@   Eff es r ->   -- | ͘   Eff es r-localHandle hh@(UnsafeMkHandleReader st) f k = do-  let UnsafeMkHandleReader st' = mapHandle hh-  orig <- get st-  bracket-    (put st' (f (mapHandle orig)))-    (\() -> put st orig)-    (\() -> k)+localHandle hh f k = do+  let UnsafeMkHandleReader st = mapHandle hh+  local st f k  askHandle ::   (e <: es, Handle h) =>   HandleReader h e ->   -- | ͘   Eff es (h es)-askHandle hh = let UnsafeMkHandleReader st = mapHandle hh in get st+askHandle hh = let UnsafeMkHandleReader st = mapHandle hh in ask st  asksHandle ::   (e1 <: es, Handle h) =>@@ -1737,11 +1778,15 @@   -- | ͘   Eff es r runHandleReader h k = do-  evalState (mapHandle h) $ \(st :: State (h es) e) -> do+  runReader (mapHandle h) $ \(st :: Reader (h es) e) -> do+    let oneWayCoerceH :: OneWayCoercion (h es) (h (e :& es))+        oneWayCoerceH = case handleDictOfHandleD (handleImpl @h) of+          MkHandleDict -> oneWayCoercion+     let coerceH :: Coercion (h es) (h (e :& es))-        coerceH = unsafeCoerce (Coercion :: Coercion (h es) (h es))+        coerceH = unsafeCoercionOfOneWayCoercion oneWayCoerceH -    let mapS :: State (h es) e' -> State (h (e :& es)) e'+    let mapS :: Reader (h es) e' -> Reader (h (e :& es)) e'         mapS = case coerceH of Coercion -> coerce      let h' :: HandleReader h (e :& es)
src/Bluefin/Internal/CloneableHandle.hs view
@@ -34,12 +34,13 @@   ((forall r. (forall e. IOE e -> h e -> Eff e r) -> IO r) -> IO a) ->   Eff es a withEffToIOCloneHandle io h k = do-  withEffToIO_ io $ \runInIO -> do-    k $ \body -> do-      runInIO $ do-        cloneHandleClass h $ \h' -> do-          cloneHandleClass io $ \io' -> do-            body (mapHandle io') (mapHandle h')+  withClonedEnv $ do+    withEffToIO_ io $ \runInIO -> do+      k $ \body -> do+        runInIO $ do+          cloneHandleClass h $ \h' -> do+            cloneHandleClass io $ \io' -> do+              body (mapHandle io') (mapHandle h')  newtype HandleCloner h1 h2 es   = MkHandleCloner@@ -98,9 +99,8 @@   cloneableHandleImpl = MkCloneableHandleD hcReader  hcReader :: HandleCloner (Reader r) (Reader r) e-hcReader = MkHandleCloner $ \(MkReader s) k -> do-  cloneHandleClass s $ \s' -> do-    useImplIn k (MkReader (mapHandle s'))+hcReader = MkHandleCloner $ \r k -> do+  useImplIn k (mapHandle r)  -- | Cloning a @HandleReader@ copies its contents to a new -- @HandleReader@.  Changes to one will not effect the other.
+ src/Bluefin/Internal/Vault.hs view
@@ -0,0 +1,37 @@+module Bluefin.Internal.Vault+  ( module Bluefin.Internal.Vault,+    Vault,+    Vault.empty,+  )+where++import Data.Kind (Type)+import Data.Vault.Strict (Vault)+import Data.Vault.Strict qualified as Vault+import GHC.Exts (Any)+import Unsafe.Coerce (unsafeCoerce)++-- Vault.Key doesn't have representational role for its "value"+-- argument. I think it should. We hack it.+--+--     https://github.com/HeinrichApfelmus/vault/issues/56+type Key :: Type -> Type+newtype Key a = MkKey (Vault.Key Any)++fromMine :: Key a -> Vault.Key a+fromMine (MkKey k) = unsafeCoerce k++toMine :: Vault.Key a -> Key a+toMine k = MkKey (unsafeCoerce k)++lookup :: Key a -> Vault -> Maybe a+lookup = Vault.lookup . fromMine++adjust :: (a -> a) -> Key a -> Vault -> Vault+adjust f = Vault.adjust f . fromMine++newKey :: IO (Key a)+newKey = fmap toMine Vault.newKey++insert :: Key a -> a -> Vault -> Vault+insert = Vault.insert . fromMine
test/Main.hs view
@@ -39,6 +39,7 @@     test_generalBracket io y     test_streamConsumeReader y     test_streamConsumeHandleReader y+    test_unliftIOReader io y  (!?) :: [a] -> Int -> Maybe a xs !? i = runPureEff $@@ -90,9 +91,8 @@     (\y2 -> local re (const "local") (yield y2 ()))     (\() -> assertEqual y "Reader local" "local" =<< ask re) --- This test confirms the buggy behavior reported in------    https://github.com/tomjaguarpaw/bluefin/issues/98+-- local run in one branch of a streamConsume should not affect ask in+-- the other branch. test_streamConsumeReader :: (e <: es) => SpecH e -> Eff es () test_streamConsumeReader spech = do   runReader @Int 0 $ \r -> do@@ -102,23 +102,23 @@           let check i = assertEqual spech "Reader local" i =<< ask r           check 0           s-          check (-100) -- Should be 0+          check 0           s-          check (-200) -- Should be 0+          check 0           local r (+ 1) $ do-            check (-199) -- Should be 1+            check 1             s-            check (-299) -- Should be 1+            check 1             local r (+ 1) $ do-              check (-298) -- Should be 2+              check 2               s-              check (-398) -- Should be 2-            check (-299) -- Should be 1+              check 2+            check 1             s-            check (-299) -- Should be 1-          check (-200) -- Should be 0+            check 1+          check 0           s-          check (-200) -- Should be 0+          check 0       )       ( \a -> do           let p = await a@@ -133,9 +133,8 @@                   forever p       ) --- This test confirms the buggy behavior reported in------    https://github.com/tomjaguarpaw/bluefin/issues/98+-- localHandle run in one branch of a streamConsume should not affect+-- askHandle in the other branch. test_streamConsumeHandleReader :: (e <: es) => SpecH e -> Eff es () test_streamConsumeHandleReader spech = do   runConstEffect @Int 0 $ \ce ->@@ -148,7 +147,7 @@                   assertEqual spech "HandleReader local" i i'             check 0             s-            check (-100) -- Should be 0+            check 0         )         ( \a -> do             let p = await a@@ -156,3 +155,23 @@             localHandle r (\(MkConstEffect i) -> MkConstEffect (subtract 100 i)) $ do               forever p         )++-- Reader should interact well with withEffToIO_+test_unliftIOReader :: (e1 <: es, e2 <: es) => IOE e1 -> SpecH e2 -> Eff es ()+test_unliftIOReader io spech = do+  let foo :: (Int -> IO ()) -> (forall r. Int -> IO r -> IO r) -> IO ()+      foo myYield mySetLocal = do+        myYield 0+        mySetLocal 1 $ do+          myYield 1+          mySetLocal 2 $ do+            myYield 2++  runReader @Int 0 $ \r -> do+    withEffToIO_ io $ \effToIO -> do+      foo+        ( \i -> effToIO $ do+            i' <- ask r+            assertEqual spech "myLocal" i i'+        )+        (\x -> effToIO . local r (const x) . effIO io)