diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,16 @@
 Changelog for Safe
 
+0.3.21, released 2024-01-18
+    #34, mark headErr/tailErr as Partial
+0.3.20, released 2024-01-14
+    #34, add headErr, tailErr
+    #33, avoid using head/tail to avoid x-partial
+0.3.19, released 2020-05-24
+    #30, undeprecate maximumDef and friends, fold*1Def
+0.3.18, released 2019-12-04
+    #27, deprecate maximumDef and friends, fold*1Def
+    #27, add maximumBounded and friends
+    Stop supporting GHC 7.4 to 7.8
 0.3.17, released 2018-03-09
     Improve the display of errors, less internal callstack
     Add a few missing Partial constraints
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Neil Mitchell 2007-2018.
+Copyright Neil Mitchell 2007-2024.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Safe [![Hackage version](https://img.shields.io/hackage/v/safe.svg?label=Hackage)](https://hackage.haskell.org/package/safe) [![Build Status](https://img.shields.io/travis/ndmitchell/safe.svg)](https://travis-ci.org/ndmitchell/safe)
+# Safe [![Hackage version](https://img.shields.io/hackage/v/safe.svg?label=Hackage)](https://hackage.haskell.org/package/safe) [![Stackage version](https://www.stackage.org/package/safe/badge/nightly?label=Stackage)](https://www.stackage.org/package/safe) [![Build status](https://img.shields.io/github/actions/workflow/status/ndmitchell/safe/ci.yml?branch=master)](https://github.com/ndmitchell/safe/actions)
 
 A library wrapping `Prelude`/`Data.List` functions that can throw exceptions, such as `head` and `!!`. Each unsafe function has up to four variants, e.g. with `tail`:
 
diff --git a/Safe.hs b/Safe.hs
--- a/Safe.hs
+++ b/Safe.hs
@@ -1,11 +1,16 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE ConstraintKinds  #-}
+
+{-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-}
+
 {- |
 A module wrapping @Prelude@/@Data.List@ functions that can throw exceptions, such as @head@ and @!!@.
-Each unsafe function has up to four variants, e.g. with @tail@:
+Each unsafe function has up to five variants, e.g. with @tail@:
 
-* @'tail' :: [a] -> [a]@, raises an error on @tail []@.
+* @'tail' :: [a] -> [a]@, raises an error on @tail []@, as provided by 'Prelude'.
 
+* @'tailErr' :: [a] -> [a]@, alias for @tail@ that doesn't trigger an @x-partial@ warning and does raise errors.
+
 * @'tailMay' :: [a] -> /Maybe/ [a]@, turns errors into @Nothing@.
 
 * @'tailDef' :: /[a]/ -> [a] -> [a]@, takes a default to return on errors.
@@ -23,15 +28,20 @@
 module Safe(
     -- * New functions
     abort, at, lookupJust, findJust, elemIndexJust, findIndexJust,
+    -- * Partial functions
+    tailErr, headErr,
     -- * Safe wrappers
     tailMay, tailDef, tailNote, tailSafe,
     initMay, initDef, initNote, initSafe,
     headMay, headDef, headNote,
     lastMay, lastDef, lastNote,
-    minimumMay, minimumDef, minimumNote,
-    maximumMay, maximumDef, maximumNote,
-    minimumByMay, minimumByDef, minimumByNote,
-    maximumByMay, maximumByDef, maximumByNote,
+    minimumMay, minimumNote,
+    maximumMay, maximumNote,
+    minimumByMay, minimumByNote,
+    maximumByMay, maximumByNote,
+    minimumBoundBy, maximumBoundBy,
+    maximumBounded, maximumBound,
+    minimumBounded, minimumBound,
     foldr1May, foldr1Def, foldr1Note,
     foldl1May, foldl1Def, foldl1Note,
     foldl1May', foldl1Def', foldl1Note',
@@ -50,6 +60,8 @@
     succMay, succDef, succNote, succSafe,
     predMay, predDef, predNote, predSafe,
     indexMay, indexDef, indexNote,
+    -- * Discouraged
+    minimumDef, maximumDef, minimumByDef, maximumByDef
     ) where
 
 import Safe.Util
@@ -89,11 +101,28 @@
 ---------------------------------------------------------------------
 -- WRAPPERS
 
+-- | Identical to 'tail', namely that fails on an empty list.
+--   Useful to avoid the @x-partial@ warning introduced in GHC 9.8.
+--
+-- > tailErr [] = error "Prelude.tail: empty list"
+-- > tailErr [1,2,3] = [2,3]
+tailErr :: Partial => [a] -> [a]
+tailErr = tail
+
+-- | Identical to 'head', namely that fails on an empty list.
+--   Useful to avoid the @x-partial@ warning introduced in GHC 9.8.
+--
+-- > headErr [] = error "Prelude.head: empty list"
+-- > headErr [1,2,3] = 1
+headErr :: Partial => [a] -> a
+headErr = head
+
 -- |
 -- > tailMay [] = Nothing
 -- > tailMay [1,3,4] = Just [3,4]
 tailMay :: [a] -> Maybe [a]
-tailMay = liftMay null tail
+tailMay [] = Nothing
+tailMay (_:xs) = Just xs
 
 -- |
 -- > tailDef [12] [] = [12]
@@ -129,7 +158,7 @@
 
 
 headMay, lastMay :: [a] -> Maybe a
-headMay = liftMay null head
+headMay = listToMaybe
 lastMay = liftMay null last
 
 headDef, lastDef :: a -> [a] -> a
@@ -144,10 +173,6 @@
 minimumMay = liftMay null minimum
 maximumMay = liftMay null maximum
 
-minimumDef, maximumDef :: Ord a => a -> [a] -> a
-minimumDef def = fromMaybe def . minimumMay
-maximumDef def = fromMaybe def . maximumMay
-
 minimumNote, maximumNote :: (Partial, Ord a) => String -> [a] -> a
 minimumNote note x = withFrozenCallStack $ fromNote note "minumumNote []" $ minimumMay x
 maximumNote note x = withFrozenCallStack $ fromNote note "maximumNote []" $ maximumMay x
@@ -156,25 +181,45 @@
 minimumByMay = liftMay null . minimumBy
 maximumByMay = liftMay null . maximumBy
 
-minimumByDef, maximumByDef :: a -> (a -> a -> Ordering) -> [a] -> a
-minimumByDef def = fromMaybe def .^ minimumByMay
-maximumByDef def = fromMaybe def .^ maximumByMay
-
 minimumByNote, maximumByNote :: Partial => String -> (a -> a -> Ordering) -> [a] -> a
 minimumByNote note f x = withFrozenCallStack $ fromNote note "minumumByNote []" $ minimumByMay f x
 maximumByNote note f x = withFrozenCallStack $ fromNote note "maximumByNote []" $ maximumByMay f x
 
+-- | The largest element of a list with respect to the
+-- given comparison function. The result is bounded by the value given as the first argument.
+maximumBoundBy :: a -> (a -> a -> Ordering) -> [a] -> a
+maximumBoundBy x f xs = maximumBy f $ x : xs
 
+-- | The smallest element of a list with respect to the
+-- given comparison function. The result is bounded by the value given as the first argument.
+minimumBoundBy :: a -> (a -> a -> Ordering) -> [a] -> a
+minimumBoundBy x f xs = minimumBy f $ x : xs
+
+-- | The largest element of a list.
+-- The result is bounded by the value given as the first argument.
+maximumBound :: Ord a => a -> [a] -> a
+maximumBound x xs = maximum $ x : xs
+
+-- | The smallest element of a list.
+-- The result is bounded by the value given as the first argument.
+minimumBound :: Ord a => a -> [a] -> a
+minimumBound x xs = minimum $ x : xs
+
+-- | The largest element of a list.
+-- The result is bounded by 'minBound'.
+maximumBounded :: (Ord a, Bounded a) => [a] -> a
+maximumBounded = maximumBound minBound
+
+-- | The largest element of a list.
+-- The result is bounded by 'maxBound'.
+minimumBounded :: (Ord a, Bounded a) => [a] -> a
+minimumBounded = minimumBound maxBound
+
 foldr1May, foldl1May, foldl1May' :: (a -> a -> a) -> [a] -> Maybe a
 foldr1May = liftMay null . foldr1
 foldl1May = liftMay null . foldl1
 foldl1May' = liftMay null . foldl1'
 
-foldr1Def, foldl1Def, foldl1Def' :: a -> (a -> a -> a) -> [a] -> a
-foldr1Def def = fromMaybe def .^ foldr1May
-foldl1Def def = fromMaybe def .^ foldl1May
-foldl1Def' def = fromMaybe def .^ foldl1May'
-
 foldr1Note, foldl1Note, foldl1Note' :: Partial => String -> (a -> a -> a) -> [a] -> a
 foldr1Note note f x = withFrozenCallStack $ fromNote note "foldr1Note []" $ foldr1May f x
 foldl1Note note f x = withFrozenCallStack $ fromNote note "foldl1Note []" $ foldl1May f x
@@ -344,3 +389,29 @@
 
 indexNote :: (Partial, Ix a) => String -> (a, a) -> a -> Int
 indexNote note x y = withFrozenCallStack $ fromNote note "indexNote, out of range" $ indexMay x y
+
+
+---------------------------------------------------------------------
+-- DISCOURAGED
+
+-- | New users are recommended to use 'minimumBound' or 'maximumBound' instead.
+minimumDef, maximumDef :: Ord a => a -> [a] -> a
+minimumDef def = fromMaybe def . minimumMay
+maximumDef def = fromMaybe def . maximumMay
+
+-- | New users are recommended to use 'minimumBoundBy' or 'maximumBoundBy' instead.
+minimumByDef, maximumByDef :: a -> (a -> a -> Ordering) -> [a] -> a
+minimumByDef def = fromMaybe def .^ minimumByMay
+maximumByDef def = fromMaybe def .^ maximumByMay
+
+
+---------------------------------------------------------------------
+-- DEPRECATED
+
+{-# DEPRECATED foldr1Def "Use @foldr1May@ instead." #-}
+{-# DEPRECATED foldl1Def "Use @foldl1May@ instead." #-}
+{-# DEPRECATED foldl1Def' "Use @foldl1May'@ instead." #-}
+foldr1Def, foldl1Def, foldl1Def' :: a -> (a -> a -> a) -> [a] -> a
+foldr1Def def = fromMaybe def .^ foldr1May
+foldl1Def def = fromMaybe def .^ foldl1May
+foldl1Def' def = fromMaybe def .^ foldl1May'
diff --git a/Safe/Exact.hs b/Safe/Exact.hs
--- a/Safe/Exact.hs
+++ b/Safe/Exact.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE ConstraintKinds  #-}
+{-# LANGUAGE TupleSections    #-}
 {- |
 Provides functions that raise errors in corner cases instead of returning \"best effort\"
 results, then provides wrappers like the "Safe" module. For example:
@@ -95,7 +96,7 @@
 -- >   | n >= 0 && n <= length xs = drop n xs
 -- >   | otherwise                = error "some message"
 dropExact :: Partial => Int -> [a] -> [a]
-dropExact i xs = withFrozenCallStack $ splitAtExact_ (addNote "" "dropExact") id (flip const) i xs
+dropExact i xs = withFrozenCallStack $ splitAtExact_ (addNote "" "dropExact") id (\_ x -> x) i xs
 
 -- |
 -- > splitAtExact n xs =
@@ -103,7 +104,7 @@
 -- >   | otherwise                = error "some message"
 splitAtExact :: Partial => Int -> [a] -> ([a], [a])
 splitAtExact i xs = withFrozenCallStack $ splitAtExact_ (addNote "" "splitAtExact")
-    (\x -> ([], x)) (\a b -> first (a:) b) i xs
+    ([],) (\a b -> first (a:) b) i xs
 
 takeExactNote :: Partial => String -> Int -> [a] -> [a]
 takeExactNote note i xs = withFrozenCallStack $ splitAtExact_ (addNote note "takeExactNote") (const []) (:) i xs
@@ -115,17 +116,17 @@
 takeExactDef def = fromMaybe def .^ takeExactMay
 
 dropExactNote :: Partial => String -> Int -> [a] -> [a]
-dropExactNote note i xs = withFrozenCallStack $ splitAtExact_ (addNote note "dropExactNote") id (flip const) i xs
+dropExactNote note i xs = withFrozenCallStack $ splitAtExact_ (addNote note "dropExactNote") id (\_ x -> x) i xs
 
 dropExactMay :: Int -> [a] -> Maybe [a]
-dropExactMay = splitAtExact_ (const Nothing) Just (flip const)
+dropExactMay = splitAtExact_ (const Nothing) Just (\_ x -> x)
 
 dropExactDef :: [a] -> Int -> [a] -> [a]
 dropExactDef def = fromMaybe def .^ dropExactMay
 
 splitAtExactNote :: Partial => String -> Int -> [a] -> ([a], [a])
 splitAtExactNote note i xs = withFrozenCallStack $ splitAtExact_ (addNote note "splitAtExactNote")
-    (\x -> ([], x)) (\a b -> first (a:) b) i xs
+    ([],) (\a b -> first (a:) b) i xs
 
 splitAtExactMay :: Int -> [a] -> Maybe ([a], [a])
 splitAtExactMay = splitAtExact_ (const Nothing)
diff --git a/Safe/Foldable.hs b/Safe/Foldable.hs
--- a/Safe/Foldable.hs
+++ b/Safe/Foldable.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE CPP              #-}
 {-# LANGUAGE ConstraintKinds  #-}
 {- |
 'Foldable' functions, with wrappers like the "Safe" module.
@@ -11,12 +10,17 @@
     foldl1May, foldl1Def, foldl1Note,
     foldr1May, foldr1Def, foldr1Note,
     findJustDef, findJustNote,
-    minimumMay, minimumDef, minimumNote,
-    maximumMay, maximumDef, maximumNote,
-    minimumByMay, minimumByDef, minimumByNote,
-    maximumByMay, maximumByDef, maximumByNote,
+    minimumMay, minimumNote,
+    maximumMay, maximumNote,
+    minimumByMay, minimumByNote,
+    maximumByMay, maximumByNote,
+    maximumBoundBy, minimumBoundBy,
+    maximumBounded, maximumBound,
+    minimumBounded, minimumBound,
+    -- * Discouraged
+    minimumDef, maximumDef, minimumByDef, maximumByDef,
     -- * Deprecated
-    foldl1Safe, foldr1Safe, findJustSafe
+    foldl1Safe, foldr1Safe, findJustSafe,
     ) where
 
 import Safe.Util
@@ -33,52 +37,64 @@
 fromNote :: Partial => String -> String -> Maybe a -> a
 fromNote = fromNoteModule "Safe.Foldable"
 
-isNull :: Foldable t => t a -> Bool
-#if __GLASGOW_HASKELL__ < 710
-isNull = null . toList
-#else
-isNull = F.null
-#endif
 
 ---------------------------------------------------------------------
 -- WRAPPERS
 
 foldl1May, foldr1May :: Foldable t => (a -> a -> a) -> t a -> Maybe a
-foldl1May = liftMay isNull . F.foldl1
-foldr1May = liftMay isNull . F.foldr1
+foldl1May = liftMay F.null . F.foldl1
+foldr1May = liftMay F.null . F.foldr1
 
 foldl1Note, foldr1Note :: (Partial, Foldable t) => String -> (a -> a -> a) -> t a -> a
 foldl1Note note f x = withFrozenCallStack $ fromNote note "foldl1Note on empty" $ foldl1May f x
 foldr1Note note f x = withFrozenCallStack $ fromNote note "foldr1Note on empty" $ foldr1May f x
 
-foldl1Def, foldr1Def :: Foldable t => a -> (a -> a -> a) -> t a -> a
-foldl1Def def = fromMaybe def .^ foldl1May
-foldr1Def def = fromMaybe def .^ foldr1May
-
 minimumMay, maximumMay :: (Foldable t, Ord a) => t a -> Maybe a
-minimumMay = liftMay isNull F.minimum
-maximumMay = liftMay isNull F.maximum
-
-minimumDef, maximumDef :: (Foldable t, Ord a) => a -> t a -> a
-minimumDef def = fromMaybe def . minimumMay
-maximumDef def = fromMaybe def . maximumMay
+minimumMay = liftMay F.null F.minimum
+maximumMay = liftMay F.null F.maximum
 
 minimumNote, maximumNote :: (Partial, Foldable t, Ord a) => String -> t a -> a
 minimumNote note x = withFrozenCallStack $ fromNote note "minimumNote on empty" $ minimumMay x
 maximumNote note x = withFrozenCallStack $ fromNote note "maximumNote on empty" $ maximumMay x
 
 minimumByMay, maximumByMay :: Foldable t => (a -> a -> Ordering) -> t a -> Maybe a
-minimumByMay = liftMay isNull . F.minimumBy
-maximumByMay = liftMay isNull . F.maximumBy
-
-minimumByDef, maximumByDef :: Foldable t => a -> (a -> a -> Ordering) -> t a -> a
-minimumByDef def = fromMaybe def .^ minimumByMay
-maximumByDef def = fromMaybe def .^ maximumByMay
+minimumByMay = liftMay F.null . F.minimumBy
+maximumByMay = liftMay F.null . F.maximumBy
 
 minimumByNote, maximumByNote :: (Partial, Foldable t) => String -> (a -> a -> Ordering) -> t a -> a
 minimumByNote note f x = withFrozenCallStack $ fromNote note "minimumByNote on empty" $ minimumByMay f x
 maximumByNote note f x = withFrozenCallStack $ fromNote note "maximumByNote on empty" $ maximumByMay f x
 
+-- | The largest element of a foldable structure with respect to the
+-- given comparison function. The result is bounded by the value given as the first argument.
+maximumBoundBy :: Foldable f => a -> (a -> a -> Ordering) -> f a -> a
+maximumBoundBy x f xs = maximumBy f $ x : toList xs
+
+-- | The smallest element of a foldable structure with respect to the
+-- given comparison function. The result is bounded by the value given as the first argument.
+minimumBoundBy :: Foldable f => a -> (a -> a -> Ordering) -> f a -> a
+minimumBoundBy x f xs = minimumBy f $ x : toList xs
+
+-- | The largest element of a foldable structure.
+-- The result is bounded by the value given as the first argument.
+maximumBound :: (Foldable f, Ord a) => a -> f a -> a
+maximumBound x xs = maximum $ x : toList xs
+
+-- | The smallest element of a foldable structure.
+-- The result is bounded by the value given as the first argument.
+minimumBound :: (Foldable f, Ord a) => a -> f a -> a
+minimumBound x xs = minimum $ x : toList xs
+
+-- | The largest element of a foldable structure.
+-- The result is bounded by 'minBound'.
+maximumBounded :: (Foldable f, Ord a, Bounded a) => f a -> a
+maximumBounded = maximumBound minBound
+
+-- | The largest element of a foldable structure.
+-- The result is bounded by 'maxBound'.
+minimumBounded :: (Foldable f, Ord a, Bounded a) => f a -> a
+minimumBounded = minimumBound maxBound
+
 -- |
 -- > findJust op = fromJust . find op
 findJust :: (Partial, Foldable t) => (a -> Bool) -> t a -> a
@@ -92,6 +108,25 @@
 
 
 ---------------------------------------------------------------------
+-- DISCOURAGED
+
+-- | New users are recommended to use 'minimumBound' or 'maximumBound' instead.
+minimumDef, maximumDef :: (Foldable t, Ord a) => a -> t a -> a
+minimumDef def = fromMaybe def . minimumMay
+maximumDef def = fromMaybe def . maximumMay
+
+-- | New users are recommended to use 'minimumBoundBy' or 'maximumBoundBy' instead.
+minimumByDef, maximumByDef :: Foldable t => a -> (a -> a -> Ordering) -> t a -> a
+minimumByDef def = fromMaybe def .^ minimumByMay
+maximumByDef def = fromMaybe def .^ maximumByMay
+
+-- | New users are recommended to use 'foldr1May' or 'foldl1May' instead.
+foldl1Def, foldr1Def :: Foldable t => a -> (a -> a -> a) -> t a -> a
+foldl1Def def = fromMaybe def .^ foldl1May
+foldr1Def def = fromMaybe def .^ foldr1May
+
+
+---------------------------------------------------------------------
 -- DEPRECATED
 
 {-# DEPRECATED foldl1Safe "Use @foldl f mempty@ instead." #-}
@@ -101,7 +136,6 @@
 {-# DEPRECATED foldr1Safe "Use @foldr f mempty@ instead." #-}
 foldr1Safe :: (Monoid m, Foldable t) => (m -> m -> m) -> t m -> m
 foldr1Safe fun = F.foldr fun mempty
-
 
 {-# DEPRECATED findJustSafe "Use @findJustDef mempty@ instead." #-}
 findJustSafe :: (Monoid m, Foldable t) => (m -> Bool) -> t m -> m
diff --git a/safe.cabal b/safe.cabal
--- a/safe.cabal
+++ b/safe.cabal
@@ -1,17 +1,17 @@
-cabal-version:  >= 1.18
+cabal-version:  1.18
 build-type:     Simple
 name:           safe
-version:        0.3.17
+version:        0.3.21
 license:        BSD3
 license-file:   LICENSE
 category:       Unclassified
 author:         Neil Mitchell <ndmitchell@gmail.com>
 maintainer:     Neil Mitchell <ndmitchell@gmail.com>
-copyright:      Neil Mitchell 2007-2018
+copyright:      Neil Mitchell 2007-2024
 homepage:       https://github.com/ndmitchell/safe#readme
 synopsis:       Library of safe (exception free) functions
 bug-reports:    https://github.com/ndmitchell/safe/issues
-tested-with:    GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2
+tested-with:    GHC==9.8, GHC==9.6, GHC==9.4, GHC==9.2, GHC==9.0, GHC==8.10, GHC==8.8
 description:
     A library wrapping @Prelude@/@Data.List@ functions that can throw exceptions, such as @head@ and @!!@.
     Each unsafe function has up to four variants, e.g. with @tail@:
@@ -44,7 +44,7 @@
 library
     default-language: Haskell2010
     build-depends:
-        base >= 4.5 && < 5
+        base >= 4.8 && < 5
 
     exposed-modules:
         Safe
