Safe stack operations with lens
I don't really see how lens will help you at all here, but you can certainly take advantage of monad transformers to handle part of your calculations, and make it so that you can layer you stack operation language on top of any monad. (so that you can, as I show below, print out the stack in the middle of the calculation)

{-# LANGUAGE LambdaCase #-}

module Main where

import           Control.Monad.State
import           Control.Monad.Except
import           Control.Monad.Identity

type StackMT s m a = ExceptT String (StateT [s] m) a
type StackM s a = StackMT s Identity a

push :: (Monad m) =&gt; s -&gt; StackMT s m ()
push a = modify (a:)

pop :: (Monad m) =&gt; StackMT s m s
pop = get &gt;&gt;= \case (x:xs) -&gt; put xs &gt;&gt; return x
                    _      -&gt; throwError "Stack underflow"

safely :: (Monad m) =&gt; StackMT s m a -&gt; StackMT s m (Either String a)
safely act = liftM Right act `catchError` (return . Left)

safely_ :: (Monad m) =&gt; StackMT s m a -&gt; StackMT s m ()
safely_ act = (act &gt;&gt; return ()) `catchError` (\_ -&gt; return ())

runStackT :: (Monad m) =&gt; StackMT s m a -&gt; [s] -&gt; m (Either String a, [s])
runStackT = runStateT . runExceptT

runStack :: StackM s a -&gt; [s] -&gt; (Either String a, [s])
runStack act stack = runIdentity $ runStackT act stack

runOnEmpty :: StackM s a -&gt; (Either String a, [s])
runOnEmpty = flip runStack []

runOnEmptyIO :: StackMT s IO a -&gt; IO (Either String a, [s])
runOnEmptyIO = flip runStackT []

-- Stack operations that cannot crash program.
--
-- To avoid passing stack back and forth in pure language, use State monad.
-- To avoid `if` statements for overflow checking, use Either monad.
-- To avoid code duplication use parameterized and high-order types.
main :: IO ()
main = do
    print =&lt;&lt; runOnEmptyIO (do push "Hello"
                               _ &lt;- pop
                               safely_ $ pop &gt;&gt;= push . (++" World!")
                               get &gt;&gt;= liftIO . putStrLn . ("Stack: " ++) . show
                               pop)
    print $ runOnEmpty $ do push (2::Double)
                            safely_ (pop &gt;&gt;= push . (**7))
                            pop


As you can see, I also added a few convenience methods.

Note that the way this is set up, the StackMT monad transformer has to be the outermost in the stack; that is, if you attempt to apply ListT or ReaderT to something of the form StackMT s m, then in the resulting monad push and pop won't work.