diff --git a/Test/Robot.hs b/Test/Robot.hs
--- a/Test/Robot.hs
+++ b/Test/Robot.hs
@@ -11,12 +11,9 @@
     , module Test.Robot.Types
 
       -- * Doing things
-      -- ** Basic operations
-    , Pressable(press, release)
+    , Pressable(press, release, hold)
     , moveBy
     , moveTo
-      -- ** Derived operations
-    , hold
     , tap
 
       -- * Miscellaneous
@@ -33,6 +30,9 @@
 import Test.Robot.Types
 
 
+infixr 4 `hold`  -- Allow e.g. xs ++ ys `hold` m
+
+
 -- | Represents things that can be pressed: either a key on a keyboard
 -- or a button on a mouse.
 class Pressable x where
@@ -43,6 +43,19 @@
     -- | Release a key or button.
     release :: x -> Robot ()
 
+    -- | @hold x act@ holds down @x@ while executing @act@. It is
+    -- equivalent to:
+    --
+    -- @
+    -- press x >> act >> release x
+    -- @
+    --
+    -- except @hold@ ensures that the argument is released in the event
+    -- of an exception.
+    --
+    hold :: x -> Robot a -> Robot a
+    hold = bracketRobot_ <$> press <*> release
+
 instance Pressable Key where
     press = keyboard True
     release = keyboard False
@@ -51,7 +64,25 @@
     press = button True
     release = button False
 
+-- | Press items from left-to-right, but release from right-to-left.
+--
+-- This behavior ensures the following equivalence holds:
+--
+-- @
+-- press xs >> act >> release xs
+--   === xs \`hold\` act
+--   === x1 \`hold\` x2 \`hold\` ... xn \`hold\` act
+-- @
+--
+instance Pressable x => Pressable [x] where
+    press = mapM_ press
+    release = mapM_ release . reverse
 
+    hold = foldr (.) id . map hold
+    --hold [] = id
+    --hold (x:xs) = hold x . hold xs
+
+
 -- | Move the pointer by an offset.
 moveBy :: Int -> Int -> Robot ()
 moveBy = motion True
@@ -60,24 +91,6 @@
 moveTo :: Int -> Int -> Robot ()
 moveTo = motion False
 
-
--- | @hold x act@ holds down @x@ while executing @act@. It is equivalent
--- to the code:
---
--- @
--- press x
--- act
--- release x
--- @
---
--- For example, you type some text in ALL CAPS using:
---
--- @
--- hold _Shift $ mapM_ tap [_D, _U, _C, _K, _S]
--- @
---
-hold :: Pressable x => x -> Robot a -> Robot a
-hold = bracketRobot_ <$> press <*> release
 
 -- | Press the argument, then release it.
 tap :: Pressable x => x -> Robot ()
diff --git a/Test/Robot/Internal.hs b/Test/Robot/Internal.hs
--- a/Test/Robot/Internal.hs
+++ b/Test/Robot/Internal.hs
@@ -68,7 +68,8 @@
 keyboard :: Bool -> Key -> Robot ()
 keyboard press key = mkRobot $ \(c, keymap) -> do
     case M.lookup (rawKey key) keymap of
-        Nothing -> error $ "Unknown keysym: " ++ show (rawKey key)
+        Nothing -> error $ "keysym " ++ show (rawKey key)
+                            ++ " does not exist on keyboard layout"
         Just keycode -> X.keyboard c press keycode
 
 button :: Bool -> Button -> Robot ()
diff --git a/examples/Jack.hs b/examples/Jack.hs
new file mode 100644
--- /dev/null
+++ b/examples/Jack.hs
@@ -0,0 +1,46 @@
+-- | A simple typing automaton. Supports ASCII letters, spaces and newlines.
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.IO.Class (liftIO)
+import Data.Ratio ((%))
+import System.Random (randomRIO)
+import Test.Robot
+
+main :: IO ()
+main = runRobot . forever $ do
+    typeText fudge "All work and no play makes Jack a dull boy\n"
+    sleep . (% 5) =<< rand (1, 10)
+  where
+    -- This program is a horrible typist
+    fudge = do
+        sleep . (1 %) =<< rand (2, 10)
+        roll <- rand (0, 100)
+        when (roll `elem` [0..3]) $ tap _Space
+        when (roll == 4) $ tap _Return
+
+typeText
+    :: Robot ()  -- ^ Action to perform between each key press
+    -> String    -- ^ Input string
+    -> Robot ()
+typeText pause = foldr combine (return ()) . map (tapShift . charToKey)
+  where
+    combine a b = a >> pause >> b
+
+tapShift :: (Bool, Key) -> Robot ()
+tapShift (False, k) = tap k
+tapShift (True , k) = _Shift `hold` tap k
+
+charToKey :: Char -> (Bool, Key)
+charToKey c
+  | 'A' <= c && c <= 'Z' = (True, k)
+  | 'a' <= c && c <= 'z' = (False, k)
+  | otherwise = (False, case c of
+        ' ' -> _Space
+        '\n' -> _Return
+        _ -> error $ "key conversion not implemented for " ++ show c)
+  where
+    k = customKey $ fromIntegral (fromEnum c)
+
+rand :: (Integer, Integer) -> Robot Integer
+rand = liftIO . randomRIO
diff --git a/examples/Vim.hs b/examples/Vim.hs
new file mode 100644
--- /dev/null
+++ b/examples/Vim.hs
@@ -0,0 +1,38 @@
+-- | Do some funny things with the keyboard.
+
+import Control.Monad (replicateM_)
+import Test.Robot
+
+main = runRobot $ do
+
+    pause
+
+    -- Let's open Vim!
+    mapM_ t [_V, _I, _M]
+    t _Return
+
+    pause
+
+    -- Enter some text...
+    t _I; sleep 1
+    _Shift `hold` do
+        mapM_ t [_D, _U, _C, _K, _S]
+    t _Escape
+
+    pause
+
+    -- Undo and redo
+    replicateM_ 3 $ do
+        t _U
+        t [_Ctrl, _R]  -- Alternatively: _Ctrl `hold` tap _R
+
+    pause
+
+    -- Quit
+    _Shift `hold` do
+        t _Z
+        t _Q
+
+  where
+    t k = sleep 0.25 >> tap k
+    pause = sleep 2
diff --git a/robot.cabal b/robot.cabal
--- a/robot.cabal
+++ b/robot.cabal
@@ -1,5 +1,5 @@
 Name:                robot
-Version:             1.1
+Version:             1.2
 Synopsis:            Simulate keyboard and mouse events
 Homepage:            https://github.com/lfairy/robot
 License:             OtherLicense
