diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.12.0
+
+* Add `asks` and `local`
+
 ## 0.0.11.0
 
 * Add `withEffToIO_`, `useImplUnder`, `makeOp`
diff --git a/bluefin-internal.cabal b/bluefin-internal.cabal
--- a/bluefin-internal.cabal
+++ b/bluefin-internal.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               bluefin-internal
-version:            0.0.11.0
+version:            0.0.12.0
 license:            MIT
 license-file:       LICENSE
 author:             Tom Ellis
diff --git a/src/Bluefin/Internal.hs b/src/Bluefin/Internal.hs
--- a/src/Bluefin/Internal.hs
+++ b/src/Bluefin/Internal.hs
@@ -75,6 +75,8 @@
   Eff es a
 withEffToIO' io k = withEffToIO k io
 
+-- | This is equivalent to the 'withRunInIO' method of
+-- 'MonadUnliftIO', but written in Bluefin-style.
 withEffToIO_ ::
   (e :> es) =>
   IOE e ->
@@ -146,18 +148,18 @@
   bv <- effIO io newEmptyMVar
 
   let t1 :: forall e. IOE e -> Eff (e :& es) r
-      t1 io' = forEach (useImplWithin m1) $ \a -> effIO io' $ do
+      t1 io' = forEach (useImplUnder . 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
+        forEach (useImplUnder . m2 ainit) $ \b_ -> effIO io' $ do
           putMVar bv b_
           takeMVar av
 
-  race (useImplWithin t1) (useImplWithin t2) io
+  race (useImplUnder . t1) (useImplUnder . t2) io
 
 -- | Old name for 'consumeStream'.  @receiveStream@ will be deprecated
 -- in a future version.
@@ -184,7 +186,7 @@
   Eff es r
 zipCoroutines c m1 m2 = do
   connectCoroutines m1 $ \a1 c1 -> do
-    connectCoroutines (useImplWithin m2) $ \a2 c2 -> do
+    connectCoroutines (useImplUnder . m2) $ \a2 c2 -> do
       evalState (a1, a2) $ \ass -> do
         forever $ do
           as <- get ass
@@ -250,6 +252,10 @@
   Eff es r
 withMonadFail f m = unEffReader m f
 
+-- | This function is really very unsafe and for internal use only.
+-- Unlike the similarly-named @unsafeEff_@ function from @effectful@,
+-- which is provided for use in user code, this function must never be
+-- used in user code.
 unsafeRemoveEff :: Eff (e :& es) a -> Eff es a
 unsafeRemoveEff = UnsafeMkEff . unsafeUnEff
 
@@ -690,7 +696,7 @@
   (forall e. StateSource e -> Eff (e :& es) a) ->
   -- | ͘
   Eff es a
-withStateSource f = unsafeRemoveEff (f StateSource)
+withStateSource f = useImplIn f StateSource
 
 -- |
 -- @
@@ -1155,7 +1161,7 @@
   (forall e. IOE e -> Eff (e :& es) a) ->
   -- | ͘
   Eff es a
-unsafeProvideIO eff = unsafeRemoveEff (eff MkIOE)
+unsafeProvideIO eff = useImplIn eff MkIOE
 
 connect ::
   (forall e1. Coroutine a b e1 -> Eff (e1 :& es) r1) ->
@@ -1241,21 +1247,58 @@
   Eff es ()
 tell (Writer y) = yield y
 
-newtype Reader r (e :: Effects) = MkReader r
-
-instance Handle (Reader r) where
-  mapHandle (MkReader r) = MkReader r
+newtype Reader r e = MkReader (State r e)
+  deriving newtype (Handle)
 
 runReader ::
-  -- | ͘
+  -- | Initial value for @Reader@.
   r ->
   (forall e. Reader r e -> Eff (e :& es) a) ->
   Eff es a
-runReader r f = unsafeRemoveEff (f (MkReader r))
+runReader r f = evalState r (f . MkReader)
 
+-- | Read the value.  Note that @ask@ has the property that these two
+-- operations are always equivalent:
+--
+-- @
+-- do
+--   r1 <- ask re
+--   r2 <- ask re
+--   pure (r1, r2)
+-- @
+--
+-- @
+-- do
+--   r <- ask re
+--   pure (r, r)
+-- @
 ask ::
   (e :> es) =>
   -- | ͘
   Reader r e ->
   Eff es r
-ask (MkReader r) = pure r
+ask (MkReader st) = get st
+
+-- | Read the value modified by a function
+asks ::
+  (e :> es) =>
+  Reader r e ->
+  -- | Read the value modified by this function
+  (r -> a) ->
+  Eff es a
+asks (MkReader st) f = fmap f (get st)
+
+local ::
+  (e1 :> es) =>
+  Reader r e1 ->
+  -- | In the body, the reader value is modified by this function.
+  (r -> r) ->
+  -- | 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)
diff --git a/src/Bluefin/Internal/Examples.hs b/src/Bluefin/Internal/Examples.hs
--- a/src/Bluefin/Internal/Examples.hs
+++ b/src/Bluefin/Internal/Examples.hs
@@ -192,7 +192,7 @@
       (pure ())
       (\() -> effIO io (putStrLn "Released"))
       $ \() -> do
-        consumeEach (useImplWithin k) $ do
+        consumeEach (useImplUnder . k) $ do
           (x, xs) <-
             get s >>= \case
               [] -> jumpTo done
@@ -207,7 +207,7 @@
   Consume a e3 ->
   Eff es ()
 takeRec n k rec =
-  withJump $ \done -> evalState n $ \s -> consumeEach (useImplWithin k) $ do
+  withJump $ \done -> evalState n $ \s -> consumeEach (useImplUnder . k) $ do
     s' <- get s
     if s' <= 0
       then jumpTo done
@@ -244,7 +244,7 @@
     mapRec (subtract 1) $
       takeRec 3 $
         traverseRec (effIO io . print) $
-          useImplWithin x
+          useImplUnder . x
 
 awaitExample :: IO ()
 awaitExample = runEff $ \io -> do
@@ -507,7 +507,7 @@
   incCounter1 c
   incCounter1 c
 
--- > exampeleCounter1
+-- > exampleCounter1
 -- 3
 
 -- Counter 2
@@ -566,6 +566,32 @@
 -- > exampleCounter3
 -- 10
 
+-- Counter 3B
+
+newtype Counter3B e = MkCounter3B (IOE e)
+
+incCounter3B :: (e :> es) => Counter3B e -> Eff es ()
+incCounter3B (MkCounter3B io) =
+  effIO io (putStrLn "You tried to increment the counter")
+
+runCounter3B ::
+  (e1 :> es) =>
+  IOE e1 ->
+  (forall e. Counter3B e -> Eff (e :& es) r) ->
+  Eff es r
+runCounter3B io k = useImplIn k (MkCounter3B (mapHandle io))
+
+exampleCounter3B :: IO ()
+exampleCounter3B = runEff $ \io -> runCounter3B io $ \c -> do
+  incCounter3B c
+  incCounter3B c
+  incCounter3B c
+
+-- ghci> exampleCounter3B
+-- You tried to increment the counter
+-- You tried to increment the counter
+-- You tried to increment the counter
+
 -- Counter 4
 
 data Counter4 e
@@ -812,15 +838,22 @@
 -- FileSystem
 
 data FileSystem es = MkFileSystem
-  { readFileImpl :: FilePath -> Eff es String,
-    writeFileImpl :: FilePath -> String -> Eff es ()
+  { readFileImpl :: forall e. FilePath -> Eff (e :& es) String,
+    writeFileImpl :: forall e. FilePath -> String -> Eff (e :& es) ()
   }
 
+instance Handle FileSystem where
+  mapHandle fs = MkFileSystem {
+    readFileImpl = \fp -> useImplUnder (readFileImpl fs fp),
+    writeFileImpl = \fp s -> useImplUnder (writeFileImpl fs fp s)
+    }
+
 readFile :: (e :> es) => FileSystem e -> FilePath -> Eff es String
-readFile fs filepath = useImpl (readFileImpl fs filepath)
+readFile fs filepath = makeOp (readFileImpl (mapHandle fs) filepath)
 
 writeFile :: (e :> es) => FileSystem e -> FilePath -> String -> Eff es ()
-writeFile fs filepath contents = useImpl (writeFileImpl fs filepath contents)
+writeFile fs filepath contents =
+  makeOp (writeFileImpl (mapHandle fs) filepath contents)
 
 runFileSystemPure ::
   (e1 :> es) =>
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -77,7 +77,7 @@
   Eff es Bool
 runTests f y = do
   ((), All passedAll) <- runWriter $ \passedAllSoFar -> do
-    forEach (useImplWithin f) $ \(name, mFailure) -> do
+    forEach (useImplUnder . f) $ \(name, mFailure) -> do
       let passed = case mFailure of
             Just _ -> False
             Nothing -> True
@@ -104,7 +104,7 @@
   (forall e1. SpecH e1 -> Eff (e1 :& es) ()) ->
   Eff es ()
 runSpecH ioe f = do
-  passed <- forEach (runTests (useImplWithin f)) $ \text ->
+  passed <- forEach (runTests (useImplUnder . f)) $ \text ->
     effIO ioe (putStrLn text)
 
   effIO ioe $ case passed of
