diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,21 @@
+# ChangeLog for unliftio-core
+
+## 0.2.1.0
+
+* Added `Control.Monad.IO.Unlift.liftIOOp`
+
+## 0.2.0.2
+
+* Widen `base` upperbound to `< 4.17` to support ghc-9.2.
+
+## 0.2.0.1
+
+* Remove faulty default implementation of `withRunInIO` [#56](https://github.com/fpco/unliftio/issues/56)
+
+## 0.2.0.0
+
+* Move `askUnliftIO` out of class [#55](https://github.com/fpco/unliftio/issues/55)
+
 ## 0.1.2.0
 
 * Add `wrappedWithRunInIO`.
diff --git a/src/Control/Monad/IO/Unlift.hs b/src/Control/Monad/IO/Unlift.hs
--- a/src/Control/Monad/IO/Unlift.hs
+++ b/src/Control/Monad/IO/Unlift.hs
@@ -4,10 +4,12 @@
 module Control.Monad.IO.Unlift
   ( MonadUnliftIO (..)
   , UnliftIO (..)
+  , askUnliftIO
   , askRunInIO
   , withUnliftIO
   , toIO
   , wrappedWithRunInIO
+  , liftIOOp
   , MonadIO (..)
   ) where
 
@@ -34,55 +36,45 @@
 -- 'MonadUnliftIO' to 'ReaderT' and 'IdentityT' transformers on top of
 -- 'IO'.
 --
--- Laws. For any value @u@ returned by 'askUnliftIO', it must meet the
+-- Laws. For any function @run@ provided by 'withRunInIO', it must meet the
 -- monad transformer laws as reformulated for @MonadUnliftIO@:
 --
--- * @unliftIO u . return = return@
+-- * @run . return = return@
 --
--- * @unliftIO u (m >>= f) = unliftIO u m >>= unliftIO u . f@
+-- * @run (m >>= f) = run m >>= run . f@
 --
--- The third is a currently nameless law which ensures that the
--- current context is preserved.
+-- Instances of @MonadUnliftIO@ must also satisfy the following laws:
 --
--- * @askUnliftIO >>= (\u -> liftIO (unliftIO u m)) = m@
+-- [Identity law] @withRunInIO (\\run -> run m) = m@
+-- [Inverse law]  @withRunInIO (\\_ -> m) = liftIO m@
 --
--- If you have a name for this, please submit it in a pull request for
--- great glory.
+-- As an example of an invalid instance, a naive implementation of
+-- @MonadUnliftIO (StateT s m)@ might be
 --
+-- @
+-- withRunInIO inner =
+--   StateT $ \\s ->
+--     withRunInIO $ \\run ->
+--       inner (run . flip evalStateT s)
+-- @
+--
+-- This breaks the identity law because the inner @run m@ would throw away
+-- any state changes in @m@.
+--
 -- @since 0.1.0.0
 class MonadIO m => MonadUnliftIO m where
-  {-# MINIMAL askUnliftIO | withRunInIO #-}
-  -- | Capture the current monadic context, providing the ability to
-  -- run monadic actions in 'IO'.
-  --
-  -- See 'UnliftIO' for an explanation of why we need a helper
-  -- datatype here.
-  --
-  -- @since 0.1.0.0
-  askUnliftIO :: m (UnliftIO m)
-  askUnliftIO = withRunInIO (\run -> return (UnliftIO run))
-  {-# INLINE askUnliftIO #-}
-  -- Would be better, but GHC hates us
-  -- askUnliftIO :: m (forall a. m a -> IO a)
-
   -- | Convenience function for capturing the monadic context and running an 'IO'
   -- action with a runner function. The runner function is used to run a monadic
   -- action @m@ in @IO@.
   --
   -- @since 0.1.0.0
-  {-# INLINE withRunInIO #-}
   withRunInIO :: ((forall a. m a -> IO a) -> IO b) -> m b
-  withRunInIO inner = askUnliftIO >>= \u -> liftIO (inner (unliftIO u))
+
 instance MonadUnliftIO IO where
-  {-# INLINE askUnliftIO #-}
-  askUnliftIO = return (UnliftIO id)
   {-# INLINE withRunInIO #-}
   withRunInIO inner = inner id
+
 instance MonadUnliftIO m => MonadUnliftIO (ReaderT r m) where
-  {-# INLINE askUnliftIO #-}
-  askUnliftIO = ReaderT $ \r ->
-                withUnliftIO $ \u ->
-                return (UnliftIO (unliftIO u . flip runReaderT r))
   {-# INLINE withRunInIO #-}
   withRunInIO inner =
     ReaderT $ \r ->
@@ -90,16 +82,30 @@
     inner (run . flip runReaderT r)
 
 instance MonadUnliftIO m => MonadUnliftIO (IdentityT m) where
-  {-# INLINE askUnliftIO #-}
-  askUnliftIO = IdentityT $
-                withUnliftIO $ \u ->
-                return (UnliftIO (unliftIO u . runIdentityT))
   {-# INLINE withRunInIO #-}
   withRunInIO inner =
     IdentityT $
     withRunInIO $ \run ->
     inner (run . runIdentityT)
 
+-- | Capture the current monadic context, providing the ability to
+-- run monadic actions in 'IO'.
+--
+-- See 'UnliftIO' for an explanation of why we need a helper
+-- datatype here.
+--
+-- Prior to version 0.2.0.0 of this library, this was a method in the
+-- 'MonadUnliftIO' type class. It was moved out due to
+-- <https://github.com/fpco/unliftio/issues/55>.
+--
+-- @since 0.1.0.0
+askUnliftIO :: MonadUnliftIO m => m (UnliftIO m)
+askUnliftIO = withRunInIO (\run -> return (UnliftIO run))
+{-# INLINE askUnliftIO #-}
+-- Would be better, but GHC hates us
+-- askUnliftIO :: m (forall a. m a -> IO a)
+
+
 -- | Same as 'askUnliftIO', but returns a monomorphic function
 -- instead of a polymorphic newtype wrapper. If you only need to apply
 -- the transformation on one concrete type, this function can be more
@@ -131,13 +137,16 @@
 Useful for the common case where you want to simply delegate to the
 underlying transformer.
 
+Note: You can derive 'MonadUnliftIO' for newtypes without this helper function
+in @unliftio-core@ 0.2.0.0 and later.
+
 @since 0.1.2.0
 ==== __Example__
 
 > newtype AppT m a = AppT { unAppT :: ReaderT Int (ResourceT m) a }
 >   deriving (Functor, Applicative, Monad, MonadIO)
->   -- Unfortunately, deriving MonadUnliftIO does not work.
 >
+> -- Same as `deriving newtype (MonadUnliftIO)`
 > instance MonadUnliftIO m => MonadUnliftIO (AppT m) where
 >   withRunInIO = wrappedWithRunInIO AppT unAppT
 -}
@@ -152,3 +161,17 @@
                    -> m b
 wrappedWithRunInIO wrap unwrap inner = wrap $ withRunInIO $ \run ->
   inner $ run . unwrap
+
+{- | A helper function for lifting @IO a -> IO b@ functions into any @MonadUnliftIO@.
+
+=== __Example__
+
+> liftedTry :: (Exception e, MonadUnliftIO m) => m a -> m (Either e a)
+> liftedTry m = liftIOOp Control.Exception.try m
+
+@since 0.2.1.0
+-}
+liftIOOp :: MonadUnliftIO m => (IO a -> IO b) -> m a -> m b
+liftIOOp f x = do
+  runInIO <- askRunInIO
+  liftIO $ f $ runInIO x
diff --git a/unliftio-core.cabal b/unliftio-core.cabal
--- a/unliftio-core.cabal
+++ b/unliftio-core.cabal
@@ -1,35 +1,33 @@
-cabal-version: >= 1.10
+cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.29.6.
+-- This file has been generated from package.yaml by hpack version 0.35.1.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: 9dda29b2ae88c7aba738c44b9efa373de55a416be845a7bf888fc8c108166fed
 
 name:           unliftio-core
-version:        0.1.2.0
+version:        0.2.1.0
 synopsis:       The MonadUnliftIO typeclass for unlifting monads to IO
 description:    Please see the documentation and README at <https://www.stackage.org/package/unliftio-core>
 category:       Control
 homepage:       https://github.com/fpco/unliftio/tree/master/unliftio-core#readme
 author:         Michael Snoyman, Francesco Mazzoli
 maintainer:     michael@snoyman.com
-copyright:      2017 FP Complete
+copyright:      2017-2020 FP Complete
 license:        MIT
 license-file:   LICENSE
 build-type:     Simple
 extra-source-files:
-    ChangeLog.md
     README.md
+    ChangeLog.md
 
 library
-  hs-source-dirs:
-      src
-  build-depends:
-      base >=4.5 && <4.12
-    , transformers >=0.2 && <0.6
   exposed-modules:
       Control.Monad.IO.Unlift
   other-modules:
       Paths_unliftio_core
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.9 && <4.17
+    , transformers >=0.2 && <0.7
   default-language: Haskell2010
