diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,8 @@
 Changelog for Extra
 
+1.6.18, released 2019-08-21
+    Make errorIO include a call stack
+    Make maximumOn and minimumOn apply the function once per element
 1.6.17, released 2019-05-31
     Add enumerate
 1.6.16, released 2019-05-25
diff --git a/extra.cabal b/extra.cabal
--- a/extra.cabal
+++ b/extra.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               extra
-version:            1.6.17
+version:            1.6.18
 license:            BSD3
 license-file:       LICENSE
 category:           Development
diff --git a/src/Control/Exception/Extra.hs b/src/Control/Exception/Extra.hs
--- a/src/Control/Exception/Extra.hs
+++ b/src/Control/Exception/Extra.hs
@@ -21,6 +21,10 @@
     catchBool, handleBool, tryBool
     ) where
 
+#if __GLASGOW_HASKELL__ >= 800
+import GHC.Stack
+#endif
+
 import Control.Exception
 import Control.Monad
 import Data.List.Extra
@@ -68,11 +72,16 @@
 
 
 -- | Like error, but in the 'IO' monad.
---   Note that while 'fail' in 'IO' raises an 'IOException', this function raises an 'ErrorCall' exception.
+--   Note that while 'fail' in 'IO' raises an 'IOException', this function raises an 'ErrorCall' exception with a call stack.
 --
--- > try (errorIO "Hello") == return (Left (ErrorCall "Hello"))
+-- > catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"
 errorIO :: Partial => String -> IO a
-errorIO = throwIO . ErrorCall
+errorIO x = withFrozenCallStack $ evaluate $ error x
+
+#if __GLASGOW_HASKELL__ < 800
+withFrozenCallStack :: a -> a
+withFrozenCallStack = id
+#endif
 
 
 -- | Retry an operation at most /n/ times (/n/ must be positive).
diff --git a/src/Data/List/Extra.hs b/src/Data/List/Extra.hs
--- a/src/Data/List/Extra.hs
+++ b/src/Data/List/Extra.hs
@@ -364,12 +364,33 @@
 nubOn f = map snd . nubBy ((==) `on` fst) . map (\x -> let y = f x in y `seq` (y, x))
 
 -- | A version of 'maximum' where the comparison is done on some extracted value.
-maximumOn :: Ord b => (a -> b) -> [a] -> a
-maximumOn f = maximumBy (compare `on` f)
+--   Raises an error if the list is empty. Only calls the function once per element.
+--
+-- > maximumOn id [] == undefined
+-- > maximumOn length ["test","extra","a"] == "extra"
+maximumOn :: (Partial, Ord b) => (a -> b) -> [a] -> a
+maximumOn f [] = error "Data.List.Extra.maximumOn: empty list"
+maximumOn f (x:xs) = g x (f x) xs
+    where
+        g v mv [] = v
+        g v mv (x:xs) | mx > mv = g x mx xs
+                      | otherwise = g v mv xs
+            where mx = f x
 
--- | A version of 'minimum' where the comparison is done on some extracted value.
-minimumOn :: Ord b => (a -> b) -> [a] -> a
-minimumOn f = minimumBy (compare `on` f)
+
+-- | A version of 'maximum' where the comparison is done on some extracted value.
+--   Raises an error if the list is empty. Only calls the function once per element.
+--
+-- > minimumOn id [] == undefined
+-- > minimumOn length ["test","extra","a"] == "a"
+minimumOn :: (Partial, Ord b) => (a -> b) -> [a] -> a
+minimumOn f [] = error "Data.List.Extra.minimumOn: empty list"
+minimumOn f (x:xs) = g x (f x) xs
+    where
+        g v mv [] = v
+        g v mv (x:xs) | mx < mv = g x mx xs
+                      | otherwise = g v mv xs
+            where mx = f x
 
 -- | A combination of 'group' and 'sort'.
 --
diff --git a/test/TestGen.hs b/test/TestGen.hs
--- a/test/TestGen.hs
+++ b/test/TestGen.hs
@@ -20,7 +20,7 @@
     testGen "stringException ['t','e','s','t',undefined]      == return \"test<Exception>\"" $ stringException ['t','e','s','t',undefined]      == return "test<Exception>"
     testGen "ignore (print 1)    == print 1" $ ignore (print 1)    == print 1
     testGen "ignore (fail \"die\") == return ()" $ ignore (fail "die") == return ()
-    testGen "try (errorIO \"Hello\") == return (Left (ErrorCall \"Hello\"))" $ try (errorIO "Hello") == return (Left (ErrorCall "Hello"))
+    testGen "catch (errorIO \"Hello\") (\\(ErrorCall x) -> return x) == return \"Hello\"" $ catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"
     testGen "retry 1 (print \"x\")  == print \"x\"" $ retry 1 (print "x")  == print "x"
     testGen "retry 3 (fail \"die\") == fail \"die\"" $ retry 3 (fail "die") == fail "die"
     testGen "whenJust Nothing  print == return ()" $ whenJust Nothing  print == return ()
@@ -149,6 +149,10 @@
     testGen "escapeJSON \"\\ttab\\nnewline\\\\\" == \"\\\\ttab\\\\nnewline\\\\\\\\\"" $ escapeJSON "\ttab\nnewline\\" == "\\ttab\\nnewline\\\\"
     testGen "escapeJSON \"\\ESC[0mHello\" == \"\\\\u001b[0mHello\"" $ escapeJSON "\ESC[0mHello" == "\\u001b[0mHello"
     testGen "\\xs -> unescapeJSON (escapeJSON xs) == xs" $ \xs -> unescapeJSON (escapeJSON xs) == xs
+    testGen "maximumOn id [] == undefined" $ erroneous $ maximumOn id []
+    testGen "maximumOn length [\"test\",\"extra\",\"a\"] == \"extra\"" $ maximumOn length ["test","extra","a"] == "extra"
+    testGen "minimumOn id [] == undefined" $ erroneous $ minimumOn id []
+    testGen "minimumOn length [\"test\",\"extra\",\"a\"] == \"a\"" $ minimumOn length ["test","extra","a"] == "a"
     testGen "groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,\"t\"),(2,\"es\"),(3,\"t\")]" $ groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,"t"),(2,"es"),(3,"t")]
     testGen "\\xs -> map fst (groupSort xs) == sort (nub (map fst xs))" $ \xs -> map fst (groupSort xs) == sort (nub (map fst xs))
     testGen "\\xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)" $ \xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)
