diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+0.4.1.6 (2021-10-17):
+    - Updating maintainer's email, url, etc
+    - Set up GithubActions for CI.
+0.4.1.4 (2015-05-30):
+    - Moved VERSION to CHANGELOG
 0.4.1 (2012-09-26):
     - Prelude.Listless: Guarded re-export of Prelude.catch with CPP
 0.4.0.1 (2010-05-31):
diff --git a/Data/List/Extras.hs b/Data/List/Extras.hs
deleted file mode 100644
--- a/Data/List/Extras.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
-----------------------------------------------------------------
---                                                  ~ 2010.04.05
--- |
--- Module      :  Data.List.Extras
--- Copyright   :  Copyright (c) 2007--2015 wren gayle romano
--- License     :  BSD3
--- Maintainer  :  wren@community.haskell.org
--- Stability   :  stable
--- Portability :  Haskell98
---
--- This module provides a single header for all @Data.List.Extras.*@
--- modules and provides a small number of other utility functions.
-----------------------------------------------------------------
-
-module Data.List.Extras
-    (
-      list
-    , module Data.List.Extras.LazyLength
-    , module Data.List.Extras.Pair
-    , module Data.List.Extras.Argmax
-    ) where
-
-import Data.List.Extras.LazyLength
-import Data.List.Extras.Pair
-import Data.List.Extras.Argmax
-
--- | Pattern matching for lists, as a first-class function. (Could
--- also be considered as a non-recursive 'foldr'.) If the list
--- argument is @[]@ then the default argument is returned; otherwise
--- the function is called with the head and tail of the list.
-list :: (a -> [a] -> b) -> b -> [a] -> b
-list _ z []     = z
-list f _ (x:xs) = f x xs
-
-----------------------------------------------------------------
------------------------------------------------------------ fin.
diff --git a/Data/List/Extras/Argmax.hs b/Data/List/Extras/Argmax.hs
deleted file mode 100644
--- a/Data/List/Extras/Argmax.hs
+++ /dev/null
@@ -1,204 +0,0 @@
-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
-----------------------------------------------------------------
---                                                  ~ 2010.10.15
--- |
--- Module      :  Data.List.Extras.ArgMax
--- Copyright   :  Copyright (c) 2007--2015 wren gayle romano
--- License     :  BSD3
--- Maintainer  :  wren@community.haskell.org
--- Stability   :  experimental
--- Portability :  Haskell98
---
--- This module provides variants of the 'maximum' and 'minimum'
--- functions which return the elements for which some function is
--- maximized or minimized.
-----------------------------------------------------------------
-
-module Data.List.Extras.Argmax
-    (
-    -- * Utility functions
-      catchNull
-    
-    -- * Generic versions
-    , argmaxBy, argmaxesBy, argmaxWithMaxBy, argmaxesWithMaxBy
-    
-    -- * Maximum variations
-    , argmax,   argmaxes,   argmaxWithMax,   argmaxesWithMax
-    
-    -- * Minimum variations
-    , argmin,   argmins,    argminWithMin,   argminsWithMin
-    
-    {- TODO: CPS and monadic variants; argmax2, argmax3,... -}
-    {- TODO: make sure argmax et al are "good consumers" for fusion -}
-    ) where
--- argmaxM       :: (Monad m, Ord b) => (a -> m b) -> [a] -> m (Maybe a)
-
-import Data.List (foldl')
-
-----------------------------------------------------------------
-----------------------------------------------------------------
-
--- | Apply a list function safely, i.e. when the list is non-empty.
--- All other functions will throw errors on empty lists, so use
--- this to make your own safe variations.
-catchNull :: ([a] -> b) -> ([a] -> Maybe b)
-{-# INLINE catchNull #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-catchNull f = \xs ->
-    case xs of
-    []  -> Nothing
-    _:_ -> Just (f xs)
-
-
--- | Minimize the number of string literals
-emptyListError :: String -> a
-{-# NOINLINE emptyListError #-}
-emptyListError fun =
-    error $ "Data.List.Extras.Argmax."++fun++": empty list"
-
-
--- | Apply a list function unsafely. For internal use.
-throwNull :: String -> (a -> [a] -> b) -> ([a] -> b)
-{-# INLINE throwNull #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-throwNull fun f = \xs ->
-    case xs of
-    []    -> emptyListError fun
-    x:xs' -> f x xs'
-
-----------------------------------------------------------------
-----------------------------------------------------------------
--- | Tail-recursive driver
-_argmaxWithMaxBy :: (b -> b -> Bool) -> (a -> b) -> a -> [a] -> (a,b)
-{-# INLINE _argmaxWithMaxBy #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-_argmaxWithMaxBy isBetterThan f =
-    \x xs -> foldl' cmp (x, f x) xs
-    where
-    cmp bfb@(_,fb) a =
-        let  fa = f a in
-        if   fa `isBetterThan` fb
-        then (a,fa)
-        else bfb
-
-
--- | Tail-recursive driver
-_argmaxesWithMaxBy :: (b -> b -> Ordering) -> (a -> b) -> a -> [a] -> ([a],b)
-{-# INLINE _argmaxesWithMaxBy #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-_argmaxesWithMaxBy isBetterEqualThan f =
-    \x xs -> foldl' cmp ([x], f x) xs
-    where
-    cmp bsfb@(bs,fb) a =
-        let  fa = f a in
-        case isBetterEqualThan fa fb of
-             GT -> ([a],  fa)
-             EQ -> (a:bs, fb)
-             _  -> bsfb
-
-
-_argmaxBy :: (b -> b -> Bool) -> (a -> b) -> a -> [a] -> a
-{-# INLINE _argmaxBy #-}
--- We use the point-free style in order to improve inlining in ghc-7.
-_argmaxBy k f = (fst .) . _argmaxWithMaxBy k f
-
-
-_argmaxesBy :: (b -> b -> Ordering) -> (a -> b) -> a -> [a] -> [a]
-{-# INLINE _argmaxesBy #-}
--- We use the point-free style in order to improve inlining in ghc-7.
-_argmaxesBy k f = (fst .) . _argmaxesWithMaxBy k f
-
-----------------------------------------------------------------
-----------------------------------------------------------------
-
-bool    :: (a -> a -> Ordering) -> (a -> a -> Bool)
-bool ord = \a b -> ord a b == GT
-
-
--- | Return an element of the list which maximizes the function
--- according to a user-defined ordering.
-argmaxBy        :: (b -> b -> Ordering) -> (a -> b) -> [a] -> a
-argmaxBy   ord f = throwNull "argmaxBy"
-                 $ _argmaxBy (bool ord) f
-
-
--- | Return all elements of the list which maximize the function
--- according to a user-defined ordering.
-argmaxesBy      :: (b -> b -> Ordering) -> (a -> b) -> [a] -> [a]
-argmaxesBy ord f = throwNull "argmaxesBy"
-                 $ _argmaxesBy ord f
-
-
--- | Return an element of the list which maximizes the function
--- according to a user-defined ordering, and return the value of
--- the function at that element as well.
-argmaxWithMaxBy        :: (b -> b -> Ordering) -> (a -> b) -> [a] -> (a, b)
-argmaxWithMaxBy   ord f = throwNull "argmaxWithMaxBy" 
-                        $ _argmaxWithMaxBy (bool ord) f
-
-
--- | Return all elements of the list which maximize the function
--- according to a user-defined ordering, and return the value of
--- the function at those elements as well.
-argmaxesWithMaxBy      :: (b -> b -> Ordering) -> (a -> b) -> [a] -> ([a], b)
-argmaxesWithMaxBy ord f = throwNull "argmaxesWithMaxBy"
-                        $ _argmaxesWithMaxBy ord f
-
-----------------------------------------------------------------
--- SPECIALIZE on b \in {Int,Integer,Float,Double} for the four
--- functions below nearly doubles the library size (about +21kB).
--- For a basic utility library that's a bit excessive, though if
--- we break the argmax stuff out from list-extras then we might go
--- through with it for performance sake.
-
--- | Return an element of the list which maximizes the function.
-argmax    :: (Ord b) => (a -> b) -> [a] -> a
-argmax   f = throwNull "argmax"
-           $ _argmaxBy (>) f
-
--- | Return all elements of the list which maximize the function.
-argmaxes  :: (Ord b) => (a -> b) -> [a] -> [a]
-argmaxes f = throwNull "argmaxes"
-           $ _argmaxesBy compare f
-
-
--- | Return an element of the list which maximizes the function,
--- and return the value of the function at that element as well.
-argmaxWithMax    :: (Ord b) => (a -> b) -> [a] -> (a, b)
-argmaxWithMax   f = throwNull "argmaxWithMax" 
-                  $ _argmaxWithMaxBy (>) f
-
-
--- | Return all elements of the list which maximize the function,
--- and return the value of the function at those elements as well.
-argmaxesWithMax  :: (Ord b) => (a -> b) -> [a] -> ([a], b)
-argmaxesWithMax f = throwNull "argmaxesWithMax"
-                  $ _argmaxesWithMaxBy compare f
-
-----------------------------------------------------------------
-
--- | Return an element of the list which minimizes the function.
-argmin   :: (Ord b) => (a -> b) -> [a] -> a
-argmin  f = throwNull "argmax"
-          $ _argmaxBy (<) f
-
--- | Return all elements of the list which minimize the function.
-argmins  :: (Ord b) => (a -> b) -> [a] -> [a]
-argmins f = throwNull "argmins"
-          $ _argmaxesBy (flip compare) f
-
-
--- | Return an element of the list which minimizes the function,
--- and return the value of the function at that element as well.
-argminWithMin   :: (Ord b) => (a -> b) -> [a] -> (a, b)
-argminWithMin  f = throwNull "argminWithMin"
-                 $ _argmaxWithMaxBy (<) f
-
--- | Return all elements of the list which minimize the function,
--- and return the value of the function at those elements as well.
-argminsWithMin  :: (Ord b) => (a -> b) -> [a] -> ([a], b)
-argminsWithMin f = throwNull "argminsWithMin"
-                 $ _argmaxesWithMaxBy (flip compare) f
-
-----------------------------------------------------------------
------------------------------------------------------------ fin.
diff --git a/Data/List/Extras/LazyLength.hs b/Data/List/Extras/LazyLength.hs
deleted file mode 100644
--- a/Data/List/Extras/LazyLength.hs
+++ /dev/null
@@ -1,145 +0,0 @@
-
--- 2008.10.12: GHC 6.10 breaks -fno-warn-orphans so that it no
--- longer suppresses the warnings for orphaned RULES. Hence -Werror
--- will make things crash on those systems, and even if that's
--- removed then -Wall will send up too many false positives which
--- may disconcert users.
-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
-
--- Unfortunately GHC < 6.10 needs -fglasgow-exts in order to actually
--- parse RULES (see -ddump-rules); the -frewrite-rules flag only
--- enables the application of rules, instead of doing what we want.
--- Apparently this is fixed in 6.10.
---
--- http://hackage.haskell.org/trac/ghc/ticket/2213
--- http://www.mail-archive.com/glasgow-haskell-users@haskell.org/msg14313.html
--- OPTIONS_GHC -O2 -fglasgow-exts -frewrite-rules
-
-----------------------------------------------------------------
---                                                  ~ 2009.04.02
--- |
--- Module      :  Data.List.Extras.LazyLength
--- Copyright   :  Copyright (c) 2007--2015 wren gayle romano
--- License     :  BSD3
--- Maintainer  :  wren@community.haskell.org
--- Stability   :  stable
--- Portability :  Haskell98
---
--- This module provides least-strict functions for getting a list's
--- length and doing natural things with it.
---
--- The regular version of @length@ will traverse the entire spine
--- of the list in order to return an answer. For comparing the
--- length against some bound, that is by far too strict. Being too
--- strict can cause a space leak by expanding a lazy list before
--- necessary (or more than is ever necessary). And it can lead to
--- unnecessarily non-terminating programs when trying to determine
--- if an infinite list is longer or shorter than some finite bound.
---
--- A nicer version of @length@ would return some lazy approximation
--- of an answer which retains the proper semantics. An option for
--- doing this is to return Peano integers which can be decremented
--- as much as necessary and no further (i.e. at most one more than
--- the bound). Of course, Peano integers are woefully inefficient.
--- This module provides functions with the same lazy effect but
--- does so efficiently instead.
---
--- As of version 0.3.0 the GHC rules to automatically rewrite
--- certain calls to 'length' into our least-strict versions have
--- been /removed/ for more consistent and predictable semantics.
--- All clients should explicitly call these least-strict functions
--- if they want the least-strict behavior.
-----------------------------------------------------------------
-
-module Data.List.Extras.LazyLength
-    ( lengthBound, lengthCompare
-    ) where
-
-
-----------------------------------------------------------------
-----------------------------------------------------------------
--- | A variant of 'length' which is least-strict for comparing
--- against a boundary length.
---
--- @lengthBound@ is polymorphic in the return of the helper
--- function so we can use 'compare' as well as '>', '>=', '==',
--- '/=', '<=', '<'. If you want to use any other functions, know
--- that we only preserve the ordering of the list's length vs the
--- boundary length and so the function should not rely on the true
--- values of either of the numbers being compared.
-
-lengthBound :: Int -> (Int -> Int -> a) -> [b] -> a
-lengthBound n cmp xs
-    | n < 0       = case xs of
-                    []    -> cmp n 0
-                    (_:_) -> cmp n 1
-    | otherwise   = go n xs
-    where
-    go n' []      = cmp n' 0
-    go 0  (_:_)   = cmp 0  1
-    go n' (_:xs') = (go $! n'-1) xs'
-
-{- bad RULES
--- The rules themselves are correct but they alter program semantics
--- regarding bottoms, depending on whether they fire or not.
-
-"lengthBound/(>)"      forall n xs. n >  length xs = lengthBound n (>)  xs
-"lengthBound/(>=)"     forall n xs. n >= length xs = lengthBound n (>=) xs
-"lengthBound/(==)"     forall n xs. n == length xs = lengthBound n (==) xs
-"lengthBound/(/=)"     forall n xs. n /= length xs = lengthBound n (/=) xs
-"lengthBound/(<=)"     forall n xs. n <= length xs = lengthBound n (<=) xs
-"lengthBound/(<)"      forall n xs. n <  length xs = lengthBound n (<)  xs
-"lengthBound/compare"  forall n xs.
-                          compare n (length xs) = lengthBound n compare xs
-
-"lengthBound\\(>)"     forall n xs. length xs >  n = lengthBound n (<)  xs
-"lengthBound\\(>=)"    forall n xs. length xs >= n = lengthBound n (<=) xs
-"lengthBound\\(==)"    forall n xs. length xs == n = lengthBound n (==) xs
-"lengthBound\\(/=)"    forall n xs. length xs /= n = lengthBound n (/=) xs
-"lengthBound\\(<=)"    forall n xs. length xs <= n = lengthBound n (>=) xs
-"lengthBound\\(<)"     forall n xs. length xs <  n = lengthBound n (>)  xs
-"lengthBound\\compare" forall n xs.
-                   compare (length xs) n = lengthBound n (flip compare) xs
-    -}
-
-
-----------------------------------------------------------------
-----------------------------------------------------------------
--- | A variant of 'length' which is least-strict for comparing
--- the lengths of two lists. This is as strict as the length of the
--- shorter list (which allows comparing an infinite list against a
--- finite list).
---
--- If you're going to immediately follow this with a 'zip' function
--- then see "Data.List.Extras.Pair" instead.
-
-lengthCompare              :: [a] -> [b] -> Ordering
-lengthCompare []     []     = EQ
-lengthCompare (_:_)  []     = GT
-lengthCompare []     (_:_)  = LT
-lengthCompare (_:xs) (_:ys) = lengthCompare xs ys
-
-
-{- bad RULES
--- The rules themselves are correct but they alter program semantics
--- regarding bottoms, depending on whether they fire or not.
-
-"lengthCompare/(>)"  forall xs ys.
-                            length xs >  length ys = lengthCompare xs ys == GT
-"lengthCompare/(>=)" forall xs ys.
-                            length xs >= length ys = lengthCompare xs ys /= LT
-"lengthCompare/(==)" forall xs ys.
-                            length xs == length ys = lengthCompare xs ys == EQ
-"lengthCompare/(/=)" forall xs ys.
-                            length xs /= length ys = lengthCompare xs ys /= EQ
-"lengthCompare/(<=)" forall xs ys.
-                            length xs <= length ys = lengthCompare xs ys /= GT
-"lengthCompare/(<)"  forall xs ys.
-                            length xs <  length ys = lengthCompare xs ys == LT
-
-"lengthCompare/compare"  forall xs ys.
-                         compare (length xs) (length ys) = lengthCompare xs ys
-    -}
-
-----------------------------------------------------------------
------------------------------------------------------------ fin.
diff --git a/Data/List/Extras/Pair.hs b/Data/List/Extras/Pair.hs
deleted file mode 100644
--- a/Data/List/Extras/Pair.hs
+++ /dev/null
@@ -1,136 +0,0 @@
-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
-----------------------------------------------------------------
---                                                  ~ 2010.11.15
--- |
--- Module      :  Data.List.Extras.Pair
--- Copyright   :  Copyright (c) 2007--2015 wren gayle romano
--- License     :  BSD3
--- Maintainer  :  wren@community.haskell.org
--- Stability   :  stable
--- Portability :  Haskell98
---
--- This module provides safe zipping functions which will fail
--- (return 'Nothing') on uneven length lists.
-----------------------------------------------------------------
-
-module Data.List.Extras.Pair
-    (
-    -- * Safe functions for zipping lists
-      pairWithBy, pairWith, pairBy, pair
-    
-    -- * Special safe zipping functions
-    , biject, biject'
-    
-    -- * New (unsafe) zipping functions
-    , zipWithBy, zipBy
-    ) where
-
-
-----------------------------------------------------------------
-----------------------------------------------------------------
--- TODO: benchmark fusion performance of:
---
---     foldr cons nil .: zipWith (,)
---     zipWithBy (,) cons nil
---
--- ...That is, the latter is a manual fusion of the former, but
--- does zip/zipWith have a special ability to fuse with the incoming
--- lists? Or can foldr fuse with consumers in ways zipWithBy can't?
-
--- | An unsafe variant of 'pairWithBy' to fill out the interface.
-zipWithBy :: (a -> b -> c)       -- tuple homomorphism
-          -> (c -> d -> d) -> d  -- list  homomorphism
-          -> [a] -> [b] -> d     -- a @zip@ function
-{-# INLINE zipWithBy #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-zipWithBy k f z = \xs ys -> zipWB xs ys id
-    where
-    zipWB (x:xs) (y:ys) cc = zipWB xs ys (cc . f (k x y))
-    zipWB _      _      cc = cc z
-
-
--- | A version of 'zip' that uses a user-defined list homomorphism.
-zipBy :: ((a,b) -> d -> d) -> d -> [a] -> [b] -> d
-{-# INLINE zipBy #-}
-zipBy = zipWithBy (,)
-
-
-----------------------------------------------------------------
-----------------------------------------------------------------
--- | A generic version of 'pair'. The first argument is a tuple
--- homomorphism (i.e. a function for how to combine values from the
--- two lists), the second two arguments form a list homomorphism
--- (i.e. so you can 'foldr' the @[c]@ list directly without actually
--- constructing it).
---
--- In order to evaluate to WHNF 'pairWithBy' is strict in both list
--- arguments, as it must be, to determine that the lists are of the
--- same length. This means it can survive one infinite list (yielding
--- 'Nothing') but that it can't survive two. The implementation is
--- very efficient and uses a tight tail-recursive loop, however
--- with extremely long lists it will be churning through heap and
--- that tightness can make it hard to interrupt (lists of 1 million
--- elements return in 1~2 seconds, but lists of 10 million can lock
--- your system up).
-
-pairWithBy :: (a -> b -> c)          -- @(,)@ tuple homomorphism
-           -> (c -> d -> d)          -- @(:)@ list  homomorphism, pt. 1
-           -> d                      -- @[]@  list  homomorphism, pt. 2
-           -> [a] -> [b] -> Maybe d  -- a safer @zip@ function
-{-# INLINE pairWithBy #-}
--- We use the explicit lambda in order to improve inlining in ghc-7.
-pairWithBy k f z = \xs ys -> pairWB xs ys id
-    where
-    -- N.B. Strict accumulators are usually awesome, but don't
-    -- even consider it when doing CPS! Making @cc@ strict degrades
-    -- performance significantly; it takes twice as long and twice
-    -- as much heap just to get to WHNF. After evaluating the spine
-    -- of the resulting list from 'pair' that drops to +10% time
-    -- and +25% heap, which is still much worse.
-    
-    pairWB (x:xs) (y:ys) cc = pairWB xs ys (cc . f (k x y))
-    pairWB []     []     cc = Just (cc z)
-    pairWB _      _      _  = Nothing
-
--- TODO: we could make this more general still by fusing @f@ and @k@, which we'd often want to do anyways if we're using this full form.
-
-----------------------------------------------------------------
-
--- | A safe version of 'zipWith'.
-pairWith :: (a -> b -> c) -> [a] -> [b] -> Maybe [c]
-{-# INLINE pairWith #-}
-pairWith f = pairWithBy f (:) []
-
-
--- | A safe version of 'zip' that uses a user-defined list homomorphism.
-pairBy :: ((a,b) -> d -> d) -> d -> [a] -> [b] -> Maybe d
-{-# INLINE pairBy #-}
-pairBy = pairWithBy (,)
-
-
--- | A safe version of 'zip'.
-pair :: [a] -> [b] -> Maybe [(a,b)]
-{-# INLINE pair #-}
-pair = pairWithBy (,) (:) []
-
-
-----------------------------------------------------------------
--- These two are just here because they're often requested, and
--- besides they're kinda cute.
-
--- | A bijection from a list of functions and a list of arguments
--- to a list of results of applying the functions bijectively.
-biject :: [a -> b] -> [a] -> Maybe [b]
-{-# INLINE biject #-}
-biject = pairWith ($) -- 'id' also works
-
-
--- | A version of 'biject' that applies functions strictly. N.B.
--- the list is still lazily evaluated, this just makes the functions
--- strict in their argument.
-biject' :: [a -> b] -> [a] -> Maybe [b]
-{-# INLINE biject' #-}
-biject' = pairWith ($!)
-
-----------------------------------------------------------------
------------------------------------------------------------ fin.
diff --git a/Prelude/Listless.hs b/Prelude/Listless.hs
deleted file mode 100644
--- a/Prelude/Listless.hs
+++ /dev/null
@@ -1,58 +0,0 @@
-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
-{-# LANGUAGE CPP #-}
-----------------------------------------------------------------
---                                                  ~ 2012.09.26
--- |
--- Module      :  Prelude.Listless
--- Copyright   :  Copyright (c) 2007--2015 wren gayle romano
--- License     :  BSD3
--- Maintainer  :  wren@community.haskell.org
--- Stability   :  stable
--- Portability :  Haskell98 (+CPP)
---
--- This module provides the "Prelude" but removing all the list
--- functions. This is helpful for modules that overload those
--- function names to work for other types. Note that on GHC 7.6 and
--- above @catch@ is no longer exported from the Prelude, and also
--- not re-exported from here; whereas, on earlier versions of GHC
--- (and non-GHC compilers) we still re-export it.
---
--- Be sure to disable the implicit importing of the prelude when
--- you import this one (by passing @-fno-implicit-prelude@ for GHC,
--- or by explicitly importing the prelude with an empty import list
--- for most implementations).
-----------------------------------------------------------------
-
-module Prelude.Listless
-    -- We have to manually specify the exports; can't export @module
-    -- Prelude@ because that'll export everything and only hide the
-    -- noted parts from this module, which is meaningless.
-    (($!), ($), (&&), (.), (=<<), Bool(..), Bounded(..), Char,
-    Double, Either(..), Enum(..), Eq(..), FilePath, Float, Floating(..),
-    Fractional(..), Functor(..), IO, IOError, Int, Integer,
-    Integral(..), Maybe(..), Monad(..), Num(..), Ord(..), Ordering(..),
-    Rational, Read(..), ReadS, Real(..), RealFloat(..), RealFrac(..),
-    Show(..), ShowS, String, (^), (^^), appendFile, asTypeOf,
-#if __GLASGOW_HASKELL__ < 706
-    catch,
-#endif
-    const, curry, either, error, even, flip, fromIntegral, fst,
-    gcd, getChar, getContents, getLine, id, interact, ioError, lcm,
-    lex, maybe, not, odd, otherwise, print, putChar, putStr, putStrLn,
-    read, readFile, readIO, readLn, readParen, reads, realToFrac,
-    seq, showChar, showParen, showString, shows, snd, subtract,
-    uncurry, undefined, until, userError, writeFile, (||))
-    
-    where
-
-import Prelude hiding
-    ((!!), (++), all, and, any, break, concat, concatMap, cycle,
-    drop, dropWhile, elem, filter, foldl, foldl1, foldr, foldr1,
-    head, init, iterate, last, length, lines, lookup, map, mapM,
-    mapM_, maximum, minimum, notElem, null, or, product, repeat,
-    replicate, reverse, scanl, scanl1, scanr, scanr1, sequence,
-    sequence_, span, splitAt, sum, tail, take, takeWhile, unlines,
-    unwords, unzip, unzip3, words, zip, zip3, zipWith, zipWith3)
-
-----------------------------------------------------------------
------------------------------------------------------------ fin.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+list-extras
+===========
+[![Hackage version](https://img.shields.io/hackage/v/list-extras.svg?style=flat)](https://hackage.haskell.org/package/list-extras)
+[![Build Status](https://github.com/wrengr/list-extras/workflows/ci/badge.svg)](https://github.com/wrengr/list-extras/actions?query=workflow%3Aci)
+[![Dependencies](https://img.shields.io/hackage-deps/v/list-extras.svg?style=flat)](http://packdeps.haskellers.com/specific?package=list-extras)
+
+The list-extras package provides a few common not-so-common functions
+for lists.
+
+## Install
+
+This is a simple package and should be easy to install.  You should
+be able to use the standard:
+
+    $> cabal install list-extras
+
+## Portability
+
+The implementation is quite portable, relying only on a few basic
+language extensions. The complete list of extensions used is:
+
+* CPP
+* Rank2Types
+* ExistentialQuantification
+
+## Links
+
+* [Website](http://wrengr.org/)
+* [Blog](http://winterkoninkje.dreamwidth.org/)
+* [Twitter](https://twitter.com/wrengr)
+* [Hackage](http://hackage.haskell.org/package/list-extras)
+* [GitHub](https://github.com/wrengr/list-extras)
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,37 +1,7 @@
 #!/usr/bin/env runhaskell
 
 module Main (main) where
-
--- <http://www.haskell.org/ghc/docs/latest/html/libraries/Cabal/Distribution-Simple.html>
 import Distribution.Simple
-
-{-
-import Distribution.PackageDescription
-import Distribution.Simple.LocalBuildInfo
-import System.Exit
-import System.Cmd
-import System.Directory
-import Control.Exception
-
-withCurrentDirectory       :: FilePath -> IO a -> IO a
-withCurrentDirectory path f = do
-    cur <- getCurrentDirectory
-    setCurrentDirectory path
-    finally f (setCurrentDirectory cur)
-
-
--- <http://blog.holdenkarau.com/2008/07/integrating-your-hunit-or-other-tests.html>
-runTestScript :: Args
-              -> Bool
-              -> PackageDescription
-              -> LocalBuildInfo
-              -> IO ExitCode
-runTestScript args flag pd lbi = withCurrentDirectory "src" (system "make")
-
-
-main :: IO ()
-main  = defaultMainWithHooks defaultUserHooks {runTests = runTestScript}
--}
 
 main :: IO ()
 main  = defaultMain
diff --git a/list-extras.cabal b/list-extras.cabal
--- a/list-extras.cabal
+++ b/list-extras.cabal
@@ -1,19 +1,19 @@
 ----------------------------------------------------------------
--- wren gayle romano <wren@community.haskell.org>   ~ 2015.05.30
+-- wren gayle romano <wren@cpan.org>                ~ 2021.10.17
 ----------------------------------------------------------------
 
--- By and large Cabal >=1.2 is fine; but >= 1.6 gives tested-with:
--- and source-repository:.
-Cabal-Version:  >= 1.6
+-- Cabal >=1.10 is required by Hackage.
+Cabal-Version:  >= 1.10
 Build-Type:     Simple
 
 Name:           list-extras
-Version:        0.4.1.4
+Version:        0.4.1.6
 Stability:      stable
-Homepage:       http://code.haskell.org/~wren/
+Homepage:       https://wrengr.org/software/hackage.html
+Bug-Reports:    https://github.com/wrengr/list-extras/issues
 Author:         wren gayle romano
-Maintainer:     wren@community.haskell.org
-Copyright:      Copyright (c) 2007--2015 wren gayle romano
+Maintainer:     wren@cpan.org
+Copyright:      Copyright (c) 2007–2021 wren gayle romano
 License:        BSD3
 License-File:   LICENSE
 
@@ -30,13 +30,23 @@
                 maintain a separate package I can share the
                 @Data.List.Extras.Foo@ namespace.
 
-Tested-With:
-    GHC ==6.12.1, GHC ==7.6.1, GHC ==7.8.0
 Extra-source-files:
-    CHANGELOG
+    README.md, CHANGELOG
+
+-- This should work as far back as GHC 6.12.1, but we don't verify that by CI.
+-- <https://github.com/wrengr/list-extras/actions?query=workflow%3Aci>
+Tested-With:
+    GHC ==8.0.2,
+    GHC ==8.2.2,
+    GHC ==8.4.4,
+    GHC ==8.6.5,
+    GHC ==8.8.4,
+    GHC ==8.10.3,
+    GHC ==9.0.1
+
 Source-Repository head
-    Type:     darcs
-    Location: http://community.haskell.org/~wren/list-extras
+    Type:     git
+    Location: https://github.com/wrengr/list-extras.git
 
 ----------------------------------------------------------------
 Flag base4
@@ -45,6 +55,8 @@
 
 ----------------------------------------------------------------
 Library
+    Default-Language: Haskell2010
+    Hs-Source-Dirs:  src
     Exposed-Modules: Prelude.Listless
                    -- , Data.List.BoehmBerarducci
                    -- , Data.List.Scott
diff --git a/src/Data/List/Extras.hs b/src/Data/List/Extras.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/List/Extras.hs
@@ -0,0 +1,37 @@
+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
+----------------------------------------------------------------
+--                                                  ~ 2021.10.17
+-- |
+-- Module      :  Data.List.Extras
+-- Copyright   :  Copyright (c) 2007--2021 wren gayle romano
+-- License     :  BSD3
+-- Maintainer  :  wren@cpan.org
+-- Stability   :  stable
+-- Portability :  Haskell98
+--
+-- This module provides a single header for all @Data.List.Extras.*@
+-- modules and provides a small number of other utility functions.
+----------------------------------------------------------------
+
+module Data.List.Extras
+    (
+      list
+    , module Data.List.Extras.LazyLength
+    , module Data.List.Extras.Pair
+    , module Data.List.Extras.Argmax
+    ) where
+
+import Data.List.Extras.LazyLength
+import Data.List.Extras.Pair
+import Data.List.Extras.Argmax
+
+-- | Pattern matching for lists, as a first-class function. (Could
+-- also be considered as a non-recursive 'foldr'.) If the list
+-- argument is @[]@ then the default argument is returned; otherwise
+-- the function is called with the head and tail of the list.
+list :: (a -> [a] -> b) -> b -> [a] -> b
+list _ z []     = z
+list f _ (x:xs) = f x xs
+
+----------------------------------------------------------------
+----------------------------------------------------------- fin.
diff --git a/src/Data/List/Extras/Argmax.hs b/src/Data/List/Extras/Argmax.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/List/Extras/Argmax.hs
@@ -0,0 +1,204 @@
+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
+----------------------------------------------------------------
+--                                                  ~ 2021.10.17
+-- |
+-- Module      :  Data.List.Extras.Argmax
+-- Copyright   :  Copyright (c) 2007--2021 wren gayle romano
+-- License     :  BSD3
+-- Maintainer  :  wren@cpan.org
+-- Stability   :  experimental
+-- Portability :  Haskell98
+--
+-- This module provides variants of the 'maximum' and 'minimum'
+-- functions which return the elements for which some function is
+-- maximized or minimized.
+----------------------------------------------------------------
+
+module Data.List.Extras.Argmax
+    (
+    -- * Utility functions
+      catchNull
+
+    -- * Generic versions
+    , argmaxBy, argmaxesBy, argmaxWithMaxBy, argmaxesWithMaxBy
+
+    -- * Maximum variations
+    , argmax,   argmaxes,   argmaxWithMax,   argmaxesWithMax
+
+    -- * Minimum variations
+    , argmin,   argmins,    argminWithMin,   argminsWithMin
+
+    {- TODO: CPS and monadic variants; argmax2, argmax3,... -}
+    {- TODO: make sure argmax et al are "good consumers" for fusion -}
+    ) where
+-- argmaxM       :: (Monad m, Ord b) => (a -> m b) -> [a] -> m (Maybe a)
+
+import Data.List (foldl')
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+
+-- | Apply a list function safely, i.e. when the list is non-empty.
+-- All other functions will throw errors on empty lists, so use
+-- this to make your own safe variations.
+catchNull :: ([a] -> b) -> ([a] -> Maybe b)
+{-# INLINE catchNull #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+catchNull f = \xs ->
+    case xs of
+    []  -> Nothing
+    _:_ -> Just (f xs)
+
+
+-- | Minimize the number of string literals
+emptyListError :: String -> a
+{-# NOINLINE emptyListError #-}
+emptyListError fun =
+    error $ "Data.List.Extras.Argmax."++fun++": empty list"
+
+
+-- | Apply a list function unsafely. For internal use.
+throwNull :: String -> (a -> [a] -> b) -> ([a] -> b)
+{-# INLINE throwNull #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+throwNull fun f = \xs ->
+    case xs of
+    []    -> emptyListError fun
+    x:xs' -> f x xs'
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+-- | Tail-recursive driver
+_argmaxWithMaxBy :: (b -> b -> Bool) -> (a -> b) -> a -> [a] -> (a,b)
+{-# INLINE _argmaxWithMaxBy #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+_argmaxWithMaxBy isBetterThan f =
+    \x xs -> foldl' cmp (x, f x) xs
+    where
+    cmp bfb@(_,fb) a =
+        let  fa = f a in
+        if   fa `isBetterThan` fb
+        then (a,fa)
+        else bfb
+
+
+-- | Tail-recursive driver
+_argmaxesWithMaxBy :: (b -> b -> Ordering) -> (a -> b) -> a -> [a] -> ([a],b)
+{-# INLINE _argmaxesWithMaxBy #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+_argmaxesWithMaxBy isBetterEqualThan f =
+    \x xs -> foldl' cmp ([x], f x) xs
+    where
+    cmp bsfb@(bs,fb) a =
+        let  fa = f a in
+        case isBetterEqualThan fa fb of
+             GT -> ([a],  fa)
+             EQ -> (a:bs, fb)
+             _  -> bsfb
+
+
+_argmaxBy :: (b -> b -> Bool) -> (a -> b) -> a -> [a] -> a
+{-# INLINE _argmaxBy #-}
+-- We use the point-free style in order to improve inlining in ghc-7.
+_argmaxBy k f = (fst .) . _argmaxWithMaxBy k f
+
+
+_argmaxesBy :: (b -> b -> Ordering) -> (a -> b) -> a -> [a] -> [a]
+{-# INLINE _argmaxesBy #-}
+-- We use the point-free style in order to improve inlining in ghc-7.
+_argmaxesBy k f = (fst .) . _argmaxesWithMaxBy k f
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+
+bool    :: (a -> a -> Ordering) -> (a -> a -> Bool)
+bool ord = \a b -> ord a b == GT
+
+
+-- | Return an element of the list which maximizes the function
+-- according to a user-defined ordering.
+argmaxBy        :: (b -> b -> Ordering) -> (a -> b) -> [a] -> a
+argmaxBy   ord f = throwNull "argmaxBy"
+                 $ _argmaxBy (bool ord) f
+
+
+-- | Return all elements of the list which maximize the function
+-- according to a user-defined ordering.
+argmaxesBy      :: (b -> b -> Ordering) -> (a -> b) -> [a] -> [a]
+argmaxesBy ord f = throwNull "argmaxesBy"
+                 $ _argmaxesBy ord f
+
+
+-- | Return an element of the list which maximizes the function
+-- according to a user-defined ordering, and return the value of
+-- the function at that element as well.
+argmaxWithMaxBy        :: (b -> b -> Ordering) -> (a -> b) -> [a] -> (a, b)
+argmaxWithMaxBy   ord f = throwNull "argmaxWithMaxBy"
+                        $ _argmaxWithMaxBy (bool ord) f
+
+
+-- | Return all elements of the list which maximize the function
+-- according to a user-defined ordering, and return the value of
+-- the function at those elements as well.
+argmaxesWithMaxBy      :: (b -> b -> Ordering) -> (a -> b) -> [a] -> ([a], b)
+argmaxesWithMaxBy ord f = throwNull "argmaxesWithMaxBy"
+                        $ _argmaxesWithMaxBy ord f
+
+----------------------------------------------------------------
+-- SPECIALIZE on b \in {Int,Integer,Float,Double} for the four
+-- functions below nearly doubles the library size (about +21kB).
+-- For a basic utility library that's a bit excessive, though if
+-- we break the argmax stuff out from list-extras then we might go
+-- through with it for performance sake.
+
+-- | Return an element of the list which maximizes the function.
+argmax    :: (Ord b) => (a -> b) -> [a] -> a
+argmax   f = throwNull "argmax"
+           $ _argmaxBy (>) f
+
+-- | Return all elements of the list which maximize the function.
+argmaxes  :: (Ord b) => (a -> b) -> [a] -> [a]
+argmaxes f = throwNull "argmaxes"
+           $ _argmaxesBy compare f
+
+
+-- | Return an element of the list which maximizes the function,
+-- and return the value of the function at that element as well.
+argmaxWithMax    :: (Ord b) => (a -> b) -> [a] -> (a, b)
+argmaxWithMax   f = throwNull "argmaxWithMax"
+                  $ _argmaxWithMaxBy (>) f
+
+
+-- | Return all elements of the list which maximize the function,
+-- and return the value of the function at those elements as well.
+argmaxesWithMax  :: (Ord b) => (a -> b) -> [a] -> ([a], b)
+argmaxesWithMax f = throwNull "argmaxesWithMax"
+                  $ _argmaxesWithMaxBy compare f
+
+----------------------------------------------------------------
+
+-- | Return an element of the list which minimizes the function.
+argmin   :: (Ord b) => (a -> b) -> [a] -> a
+argmin  f = throwNull "argmax"
+          $ _argmaxBy (<) f
+
+-- | Return all elements of the list which minimize the function.
+argmins  :: (Ord b) => (a -> b) -> [a] -> [a]
+argmins f = throwNull "argmins"
+          $ _argmaxesBy (flip compare) f
+
+
+-- | Return an element of the list which minimizes the function,
+-- and return the value of the function at that element as well.
+argminWithMin   :: (Ord b) => (a -> b) -> [a] -> (a, b)
+argminWithMin  f = throwNull "argminWithMin"
+                 $ _argmaxWithMaxBy (<) f
+
+-- | Return all elements of the list which minimize the function,
+-- and return the value of the function at those elements as well.
+argminsWithMin  :: (Ord b) => (a -> b) -> [a] -> ([a], b)
+argminsWithMin f = throwNull "argminsWithMin"
+                 $ _argmaxesWithMaxBy (flip compare) f
+
+----------------------------------------------------------------
+----------------------------------------------------------- fin.
diff --git a/src/Data/List/Extras/LazyLength.hs b/src/Data/List/Extras/LazyLength.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/List/Extras/LazyLength.hs
@@ -0,0 +1,145 @@
+
+-- 2008.10.12: GHC 6.10 breaks -fno-warn-orphans so that it no
+-- longer suppresses the warnings for orphaned RULES. Hence -Werror
+-- will make things crash on those systems, and even if that's
+-- removed then -Wall will send up too many false positives which
+-- may disconcert users.
+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
+
+-- Unfortunately GHC < 6.10 needs -fglasgow-exts in order to actually
+-- parse RULES (see -ddump-rules); the -frewrite-rules flag only
+-- enables the application of rules, instead of doing what we want.
+-- Apparently this is fixed in 6.10.
+--
+-- http://hackage.haskell.org/trac/ghc/ticket/2213
+-- http://www.mail-archive.com/glasgow-haskell-users@haskell.org/msg14313.html
+-- OPTIONS_GHC -O2 -fglasgow-exts -frewrite-rules
+
+----------------------------------------------------------------
+--                                                  ~ 2021.10.17
+-- |
+-- Module      :  Data.List.Extras.LazyLength
+-- Copyright   :  Copyright (c) 2007--2021 wren gayle romano
+-- License     :  BSD3
+-- Maintainer  :  wren@cpan.org
+-- Stability   :  stable
+-- Portability :  Haskell98
+--
+-- This module provides least-strict functions for getting a list's
+-- length and doing natural things with it.
+--
+-- The regular version of @length@ will traverse the entire spine
+-- of the list in order to return an answer. For comparing the
+-- length against some bound, that is by far too strict. Being too
+-- strict can cause a space leak by expanding a lazy list before
+-- necessary (or more than is ever necessary). And it can lead to
+-- unnecessarily non-terminating programs when trying to determine
+-- if an infinite list is longer or shorter than some finite bound.
+--
+-- A nicer version of @length@ would return some lazy approximation
+-- of an answer which retains the proper semantics. An option for
+-- doing this is to return Peano integers which can be decremented
+-- as much as necessary and no further (i.e. at most one more than
+-- the bound). Of course, Peano integers are woefully inefficient.
+-- This module provides functions with the same lazy effect but
+-- does so efficiently instead.
+--
+-- As of version 0.3.0 the GHC rules to automatically rewrite
+-- certain calls to 'length' into our least-strict versions have
+-- been /removed/ for more consistent and predictable semantics.
+-- All clients should explicitly call these least-strict functions
+-- if they want the least-strict behavior.
+----------------------------------------------------------------
+
+module Data.List.Extras.LazyLength
+    ( lengthBound, lengthCompare
+    ) where
+
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+-- | A variant of 'length' which is least-strict for comparing
+-- against a boundary length.
+--
+-- @lengthBound@ is polymorphic in the return of the helper
+-- function so we can use 'compare' as well as '>', '>=', '==',
+-- '/=', '<=', '<'. If you want to use any other functions, know
+-- that we only preserve the ordering of the list's length vs the
+-- boundary length and so the function should not rely on the true
+-- values of either of the numbers being compared.
+
+lengthBound :: Int -> (Int -> Int -> a) -> [b] -> a
+lengthBound n cmp xs
+    | n < 0       = case xs of
+                    []    -> cmp n 0
+                    (_:_) -> cmp n 1
+    | otherwise   = go n xs
+    where
+    go n' []      = cmp n' 0
+    go 0  (_:_)   = cmp 0  1
+    go n' (_:xs') = (go $! n'-1) xs'
+
+{- bad RULES
+-- The rules themselves are correct but they alter program semantics
+-- regarding bottoms, depending on whether they fire or not.
+
+"lengthBound/(>)"      forall n xs. n >  length xs = lengthBound n (>)  xs
+"lengthBound/(>=)"     forall n xs. n >= length xs = lengthBound n (>=) xs
+"lengthBound/(==)"     forall n xs. n == length xs = lengthBound n (==) xs
+"lengthBound/(/=)"     forall n xs. n /= length xs = lengthBound n (/=) xs
+"lengthBound/(<=)"     forall n xs. n <= length xs = lengthBound n (<=) xs
+"lengthBound/(<)"      forall n xs. n <  length xs = lengthBound n (<)  xs
+"lengthBound/compare"  forall n xs.
+                          compare n (length xs) = lengthBound n compare xs
+
+"lengthBound\\(>)"     forall n xs. length xs >  n = lengthBound n (<)  xs
+"lengthBound\\(>=)"    forall n xs. length xs >= n = lengthBound n (<=) xs
+"lengthBound\\(==)"    forall n xs. length xs == n = lengthBound n (==) xs
+"lengthBound\\(/=)"    forall n xs. length xs /= n = lengthBound n (/=) xs
+"lengthBound\\(<=)"    forall n xs. length xs <= n = lengthBound n (>=) xs
+"lengthBound\\(<)"     forall n xs. length xs <  n = lengthBound n (>)  xs
+"lengthBound\\compare" forall n xs.
+                   compare (length xs) n = lengthBound n (flip compare) xs
+    -}
+
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+-- | A variant of 'length' which is least-strict for comparing
+-- the lengths of two lists. This is as strict as the length of the
+-- shorter list (which allows comparing an infinite list against a
+-- finite list).
+--
+-- If you're going to immediately follow this with a 'zip' function
+-- then see "Data.List.Extras.Pair" instead.
+
+lengthCompare              :: [a] -> [b] -> Ordering
+lengthCompare []     []     = EQ
+lengthCompare (_:_)  []     = GT
+lengthCompare []     (_:_)  = LT
+lengthCompare (_:xs) (_:ys) = lengthCompare xs ys
+
+
+{- bad RULES
+-- The rules themselves are correct but they alter program semantics
+-- regarding bottoms, depending on whether they fire or not.
+
+"lengthCompare/(>)"  forall xs ys.
+                            length xs >  length ys = lengthCompare xs ys == GT
+"lengthCompare/(>=)" forall xs ys.
+                            length xs >= length ys = lengthCompare xs ys /= LT
+"lengthCompare/(==)" forall xs ys.
+                            length xs == length ys = lengthCompare xs ys == EQ
+"lengthCompare/(/=)" forall xs ys.
+                            length xs /= length ys = lengthCompare xs ys /= EQ
+"lengthCompare/(<=)" forall xs ys.
+                            length xs <= length ys = lengthCompare xs ys /= GT
+"lengthCompare/(<)"  forall xs ys.
+                            length xs <  length ys = lengthCompare xs ys == LT
+
+"lengthCompare/compare"  forall xs ys.
+                         compare (length xs) (length ys) = lengthCompare xs ys
+    -}
+
+----------------------------------------------------------------
+----------------------------------------------------------- fin.
diff --git a/src/Data/List/Extras/Pair.hs b/src/Data/List/Extras/Pair.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/List/Extras/Pair.hs
@@ -0,0 +1,136 @@
+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
+----------------------------------------------------------------
+--                                                  ~ 2021.10.17
+-- |
+-- Module      :  Data.List.Extras.Pair
+-- Copyright   :  Copyright (c) 2007--2021 wren gayle romano
+-- License     :  BSD3
+-- Maintainer  :  wren@cpan.org
+-- Stability   :  stable
+-- Portability :  Haskell98
+--
+-- This module provides safe zipping functions which will fail
+-- (return 'Nothing') on uneven length lists.
+----------------------------------------------------------------
+
+module Data.List.Extras.Pair
+    (
+    -- * Safe functions for zipping lists
+      pairWithBy, pairWith, pairBy, pair
+
+    -- * Special safe zipping functions
+    , biject, biject'
+
+    -- * New (unsafe) zipping functions
+    , zipWithBy, zipBy
+    ) where
+
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+-- TODO: benchmark fusion performance of:
+--
+--     foldr cons nil .: zipWith (,)
+--     zipWithBy (,) cons nil
+--
+-- ...That is, the latter is a manual fusion of the former, but
+-- does zip/zipWith have a special ability to fuse with the incoming
+-- lists? Or can foldr fuse with consumers in ways zipWithBy can't?
+
+-- | An unsafe variant of 'pairWithBy' to fill out the interface.
+zipWithBy :: (a -> b -> c)       -- tuple homomorphism
+          -> (c -> d -> d) -> d  -- list  homomorphism
+          -> [a] -> [b] -> d     -- a @zip@ function
+{-# INLINE zipWithBy #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+zipWithBy k f z = \xs ys -> zipWB xs ys id
+    where
+    zipWB (x:xs) (y:ys) cc = zipWB xs ys (cc . f (k x y))
+    zipWB _      _      cc = cc z
+
+
+-- | A version of 'zip' that uses a user-defined list homomorphism.
+zipBy :: ((a,b) -> d -> d) -> d -> [a] -> [b] -> d
+{-# INLINE zipBy #-}
+zipBy = zipWithBy (,)
+
+
+----------------------------------------------------------------
+----------------------------------------------------------------
+-- | A generic version of 'pair'. The first argument is a tuple
+-- homomorphism (i.e. a function for how to combine values from the
+-- two lists), the second two arguments form a list homomorphism
+-- (i.e. so you can 'foldr' the @[c]@ list directly without actually
+-- constructing it).
+--
+-- In order to evaluate to WHNF 'pairWithBy' is strict in both list
+-- arguments, as it must be, to determine that the lists are of the
+-- same length. This means it can survive one infinite list (yielding
+-- 'Nothing') but that it can't survive two. The implementation is
+-- very efficient and uses a tight tail-recursive loop, however
+-- with extremely long lists it will be churning through heap and
+-- that tightness can make it hard to interrupt (lists of 1 million
+-- elements return in 1~2 seconds, but lists of 10 million can lock
+-- your system up).
+
+pairWithBy :: (a -> b -> c)          -- @(,)@ tuple homomorphism
+           -> (c -> d -> d)          -- @(:)@ list  homomorphism, pt. 1
+           -> d                      -- @[]@  list  homomorphism, pt. 2
+           -> [a] -> [b] -> Maybe d  -- a safer @zip@ function
+{-# INLINE pairWithBy #-}
+-- We use the explicit lambda in order to improve inlining in ghc-7.
+pairWithBy k f z = \xs ys -> pairWB xs ys id
+    where
+    -- N.B. Strict accumulators are usually awesome, but don't
+    -- even consider it when doing CPS! Making @cc@ strict degrades
+    -- performance significantly; it takes twice as long and twice
+    -- as much heap just to get to WHNF. After evaluating the spine
+    -- of the resulting list from 'pair' that drops to +10% time
+    -- and +25% heap, which is still much worse.
+
+    pairWB (x:xs) (y:ys) cc = pairWB xs ys (cc . f (k x y))
+    pairWB []     []     cc = Just (cc z)
+    pairWB _      _      _  = Nothing
+
+-- TODO: we could make this more general still by fusing @f@ and @k@, which we'd often want to do anyways if we're using this full form.
+
+----------------------------------------------------------------
+
+-- | A safe version of 'zipWith'.
+pairWith :: (a -> b -> c) -> [a] -> [b] -> Maybe [c]
+{-# INLINE pairWith #-}
+pairWith f = pairWithBy f (:) []
+
+
+-- | A safe version of 'zip' that uses a user-defined list homomorphism.
+pairBy :: ((a,b) -> d -> d) -> d -> [a] -> [b] -> Maybe d
+{-# INLINE pairBy #-}
+pairBy = pairWithBy (,)
+
+
+-- | A safe version of 'zip'.
+pair :: [a] -> [b] -> Maybe [(a,b)]
+{-# INLINE pair #-}
+pair = pairWithBy (,) (:) []
+
+
+----------------------------------------------------------------
+-- These two are just here because they're often requested, and
+-- besides they're kinda cute.
+
+-- | A bijection from a list of functions and a list of arguments
+-- to a list of results of applying the functions bijectively.
+biject :: [a -> b] -> [a] -> Maybe [b]
+{-# INLINE biject #-}
+biject = pairWith ($) -- 'id' also works
+
+
+-- | A version of 'biject' that applies functions strictly. N.B.
+-- the list is still lazily evaluated, this just makes the functions
+-- strict in their argument.
+biject' :: [a -> b] -> [a] -> Maybe [b]
+{-# INLINE biject' #-}
+biject' = pairWith ($!)
+
+----------------------------------------------------------------
+----------------------------------------------------------- fin.
diff --git a/src/Prelude/Listless.hs b/src/Prelude/Listless.hs
new file mode 100644
--- /dev/null
+++ b/src/Prelude/Listless.hs
@@ -0,0 +1,58 @@
+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
+{-# LANGUAGE CPP #-}
+----------------------------------------------------------------
+--                                                  ~ 2021.10.17
+-- |
+-- Module      :  Prelude.Listless
+-- Copyright   :  Copyright (c) 2007--2021 wren gayle romano
+-- License     :  BSD3
+-- Maintainer  :  wren@cpan.org
+-- Stability   :  stable
+-- Portability :  Haskell98 (+CPP)
+--
+-- This module provides the "Prelude" but removing all the list
+-- functions. This is helpful for modules that overload those
+-- function names to work for other types. Note that on GHC 7.6 and
+-- above @catch@ is no longer exported from the Prelude, and also
+-- not re-exported from here; whereas, on earlier versions of GHC
+-- (and non-GHC compilers) we still re-export it.
+--
+-- Be sure to disable the implicit importing of the prelude when
+-- you import this one (by passing @-fno-implicit-prelude@ for GHC,
+-- or by explicitly importing the prelude with an empty import list
+-- for most implementations).
+----------------------------------------------------------------
+
+module Prelude.Listless
+    -- We have to manually specify the exports; can't export @module
+    -- Prelude@ because that'll export everything and only hide the
+    -- noted parts from this module, which is meaningless.
+    (($!), ($), (&&), (.), (=<<), Bool(..), Bounded(..), Char,
+    Double, Either(..), Enum(..), Eq(..), FilePath, Float, Floating(..),
+    Fractional(..), Functor(..), IO, IOError, Int, Integer,
+    Integral(..), Maybe(..), Monad(..), Num(..), Ord(..), Ordering(..),
+    Rational, Read(..), ReadS, Real(..), RealFloat(..), RealFrac(..),
+    Show(..), ShowS, String, (^), (^^), appendFile, asTypeOf,
+#if __GLASGOW_HASKELL__ < 706
+    catch,
+#endif
+    const, curry, either, error, even, flip, fromIntegral, fst,
+    gcd, getChar, getContents, getLine, id, interact, ioError, lcm,
+    lex, maybe, not, odd, otherwise, print, putChar, putStr, putStrLn,
+    read, readFile, readIO, readLn, readParen, reads, realToFrac,
+    seq, showChar, showParen, showString, shows, snd, subtract,
+    uncurry, undefined, until, userError, writeFile, (||))
+
+    where
+
+import Prelude hiding
+    ((!!), (++), all, and, any, break, concat, concatMap, cycle,
+    drop, dropWhile, elem, filter, foldl, foldl1, foldr, foldr1,
+    head, init, iterate, last, length, lines, lookup, map, mapM,
+    mapM_, maximum, minimum, notElem, null, or, product, repeat,
+    replicate, reverse, scanl, scanl1, scanr, scanr1, sequence,
+    sequence_, span, splitAt, sum, tail, take, takeWhile, unlines,
+    unwords, unzip, unzip3, words, zip, zip3, zipWith, zipWith3)
+
+----------------------------------------------------------------
+----------------------------------------------------------- fin.
