-- | This module allows you to zip lists with any `Traversable` data structure.
--
-- It's intended that you import it qualified:
--
-- > import qualified Data.Traversable.HeteroZip as Hetero
-- > x = Hetero.zipWith ...

module Data.Traversable.HeteroZip
  ( -- * Zipping #zipping#
    -- $zipping
    zip
  , zipErr
  , zipInf
  , zipMay
  , zipNote
  , zipWith
  , zipWithErr
  , zipWithInf
  , zipWithMay
  , zipWithNote

    -- * Enumeration #enumeration#
    -- $enumeration
  , enumerate
  , enumerateWith

    -- * Holes #holes#
    -- $holes
  , setHoles
  , unsetHoles

    -- * A note on `Traversable` #traversable#
    -- $traversable
  ) where

import Prelude hiding (zip, zipWith)

import Data.Functor.Compose (Compose(..))
import Data.List.Infinite (Infinite(..))
import Data.Maybe ( fromMaybe )
import Data.Traversable ( mapAccumL )
import GHC.Stack ( HasCallStack, withFrozenCallStack )

-- $setup
-- >>> :set -Wno-type-defaults -Wno-name-shadowing
-- >>> import Data.Maybe (isJust)
--
-- This means we don't need to repeat a noisy stack trace in test outputs:
-- >>> undefined = errorWithoutStackTrace "Prelude.undefined"

-- $zipping
--
-- A `Traversable`'s elements can be visited one at a time, and updated
-- in-place. That means we can visit them at the same time as we walk along a
-- list, and use the values in the list to update the values in the
-- `Traversable`. These functions do just that.
--
-- @zip*@ functions simply pair elements off, while @zipWith*@ functions use an
-- explicit combining function, like regular `Prelude.zip` and
-- `Prelude.zipWith`. They each have five variants to deal with the possibility
-- that the input list is shorter than the `Traversable`:
--
-- * `zip` and `zipWith` pass a `Maybe` to the combining function. `zipWith` is
--   the most general function here; all the others can be implemented as simple
--   wrappers around that.
--
-- * `zipMay` and `zipWithMay` put `Maybe`s in the result `Traversable`.
--
-- * `zipErr` and `zipWithErr` are partial, throwing errors if the list is too
--    short.
--
-- * `zipNote` and `zipWithNote` are also partial, with a custom error message.
--
-- * `zipInf` and `zipWithInf` use an `Infinite` list that will never be too
--   short.
--
-- All these functions are lazy in both the list and `Traversable` arguments,
-- and in the function application, to the extent that the `Traversable`
-- instance allows. For example:
--
-- >>> take 3 $ zip ([1, 2, 3] ++ undefined) ([1, 2, 3] ++ undefined)
-- [(Just 1,1),(Just 2,2),(Just 3,3)]
-- >>> fst <$> zip [1, 2, 3] [1, 2, undefined]
-- [Just 1,Just 2,Just 3]
-- >>> isJust . fst <$> zip [1, 2, undefined] [1, 2, undefined]
-- [True,True,True]
-- >>> snd <$> zip [1, 2, undefined] [1, 2, 3]
-- [1,2,3]

-- $more-laziness-examples
--
-- Current failures:
-- (These are similar to the above but replace an @undefined : []@ with a plain
-- @undefined@, so they have less to work with. It wouldn't be a regression if
-- they somehow succeeded but I think that's not possible.)
-- >>> isJust . fst <$> zip ([1, 2] ++ undefined) [1, 2, undefined]
-- [True,True,*** Exception: Prelude.undefined
-- >>> isJust . fst <$> zip [1, 2, undefined] ([1, 2] ++ undefined)
-- [True,True*** Exception: Prelude.undefined
-- >>> snd <$> zip ([1, 2] ++ undefined) [1, 2, 3]
-- [1,2,*** Exception: Prelude.undefined
--
-- >>> take 3 $ zipMay ([1, 2, 3] ++ undefined) ([1, 2, 3] ++ undefined)
-- [Just (1,1),Just (2,2),Just (3,3)]
-- >>> take 3 $ zipMay [1, 2] [1, 2, undefined]
-- [Just (1,1),Just (2,2),Nothing]
-- >>> fmap fst <$> zipMay [1, 2] [1, 2, undefined]
-- [Just 1,Just 2,Nothing]
-- >>> fmap fst <$> zipMay [1, 2, 3] [1, 2, undefined]
-- [Just 1,Just 2,Just 3]
-- >>> isJust <$> zipMay [1, 2, undefined] [1, 2, undefined]
-- [True,True,True]
-- >>> fmap snd <$> zipMay [1, 2, undefined] [1, 2, 3]
-- [Just 1,Just 2,Just 3]
--
-- Current failures:
-- >>> take 3 $ zipMay [1, 2] ([1, 2] ++ undefined)
-- [Just (1,1),Just (2,2)*** Exception: Prelude.undefined
-- >>> isJust <$> zipMay ([1, 2] ++ undefined) [1, 2, undefined]
-- [True,True,*** Exception: Prelude.undefined
-- >>> isJust <$> zipMay [1, 2, undefined] ([1, 2] ++ undefined)
-- [True,True*** Exception: Prelude.undefined
--
-- >>> take 3 $ zipErr ([1, 2, 3] ++ undefined) ([1, 2, 3] ++ undefined)
-- [(1,1),(2,2),(3,3)]
-- >>> fst <$> zipErr [1, 2, 3] [1, 2, undefined]
-- [1,2,3]
-- >>> snd <$> zipErr [1, 2, undefined] [1, 2, 3]
-- [1,2,3]
--
-- Current failures:
-- >>> fst <$> zipErr [1, 2, 3] ([1, 2] ++ undefined)
-- [1,2*** Exception: Prelude.undefined
-- >>> snd <$> zipErr ([1, 2] ++ undefined) [1, 2, 3]
-- [1,2,*** Exception: Prelude.undefined
--
-- >>> take 3 $ zipNote undefined ([1, 2, 3] ++ undefined) ([1, 2, 3] ++ undefined)
-- [(1,1),(2,2),(3,3)]
-- >>> fst <$> zipNote undefined [1, 2, 3] [1, 2, undefined]
-- [1,2,3]
-- >>> snd <$> zipNote undefined [1, 2, undefined] [1, 2, 3]
-- [1,2,3]
--
-- Current failures:
-- >>> fst <$> zipNote undefined [1, 2, 3] ([1, 2] ++ undefined)
-- [1,2*** Exception: Prelude.undefined
-- >>> snd <$> zipNote undefined ([1, 2] ++ undefined) [1, 2, 3]
-- [1,2,*** Exception: Prelude.undefined
--
-- >>> take 3 $ zipInf (1 :< 2 :< 3 :< undefined) ([1, 2, 3] ++ undefined)
-- [(1,1),(2,2),(3,3)]
-- >>> fst <$> zipInf (1 :< 2 :< 3 :< undefined) [1, 2, undefined]
-- [1,2,3]
-- >>> snd <$> zipInf (1 :< 2 :< undefined :< undefined) [1, 2, 3]
-- [1,2,3]
--
-- Current failures:
-- >>> fst <$> zipInf (1 :< 2 :< 3 :< undefined) ([1, 2] ++ undefined)
-- [1,2*** Exception: Prelude.undefined
-- >>> snd <$> zipInf (1 :< 2 :< undefined) [1, 2, 3]
-- [1,2,*** Exception: Prelude.undefined

-- | Zip a list with any `Traversable`, maintaining the shape of the latter.
--
-- If the list is too short, pair with `Just` values until it runs out, and then
-- `Nothing`s.
--
-- >>> zip [1, 2] (Just ())
-- Just (Just 1,())
--
-- >>> zip [] (Just ())
-- Just (Nothing,())
--
-- >>> zip [] Nothing
-- Nothing
zip :: Traversable t => [a] -> t b -> t (Maybe a, b)
zip :: forall (t :: * -> *) a b.
Traversable t =>
[a] -> t b -> t (Maybe a, b)
zip = (Maybe a -> b -> (Maybe a, b)) -> [a] -> t b -> t (Maybe a, b)
forall (t :: * -> *) a b c.
Traversable t =>
(Maybe a -> b -> c) -> [a] -> t b -> t c
zipWith (,)

-- | Zip a list with any `Traversable`, maintaining the shape of the latter.
--
-- If the list is too short, throw an error.
--
-- >>> zipErr [1, 2] (Just ())
-- Just (1,())
--
-- >>> zipErr [] (Just ())
-- Just (*** Exception: zipErr: list too short
-- CallStack (from HasCallStack):
--   zipErr, called at ...
--
-- >>> zipErr [] Nothing
-- Nothing
zipErr :: (HasCallStack, Traversable t) => [a] -> t b -> t (a, b)
zipErr :: forall (t :: * -> *) a b.
(HasCallStack, Traversable t) =>
[a] -> t b -> t (a, b)
zipErr = (HasCallStack => [a] -> t b -> t (a, b)) -> [a] -> t b -> t (a, b)
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => [a] -> t b -> t (a, b))
 -> [a] -> t b -> t (a, b))
-> (HasCallStack => [a] -> t b -> t (a, b))
-> [a]
-> t b
-> t (a, b)
forall a b. (a -> b) -> a -> b
$ String -> [a] -> t b -> t (a, b)
forall (t :: * -> *) a b.
(HasCallStack, Traversable t) =>
String -> [a] -> t b -> t (a, b)
zipNote String
"zipErr: list too short"

-- | Zip an `Infinite` list with any `Traversable`, maintaining the shape of the
-- latter.
--
-- >>> :set -XPostfixOperators
-- >>> import Data.List.Infinite ((...))
--
-- >>> zipInf (1...) (Just ())
-- Just (1,())
--
-- >>> zipInf (1...) Nothing
-- Nothing
zipInf :: Traversable t => Infinite a -> t b -> t (a, b)
zipInf :: forall (t :: * -> *) a b.
Traversable t =>
Infinite a -> t b -> t (a, b)
zipInf = (a -> b -> (a, b)) -> Infinite a -> t b -> t (a, b)
forall (t :: * -> *) a b c.
Traversable t =>
(a -> b -> c) -> Infinite a -> t b -> t c
zipWithInf (,)
-- When infinite-list 0.1.2 is released, this is heteroZip.

-- | Zip a list with any `Traversable`, maintaining the shape of the latter.
--
-- If the list is too short, start producing `Nothing`s. You can use `sequence`
-- to get a @`Maybe` (t (a, b))@; but note that this must walk the whole
-- `Traversable` before it can produce a `Just`.
--
-- >>> zipMay [1, 2] (Just ())
-- Just (Just (1,()))
--
-- >>> zipMay [] (Just ())
-- Just Nothing
--
-- >>> zipMay [] Nothing
-- Nothing
zipMay :: Traversable t => [a] -> t b -> t (Maybe (a, b))
zipMay :: forall (t :: * -> *) a b.
Traversable t =>
[a] -> t b -> t (Maybe (a, b))
zipMay = (a -> b -> (a, b)) -> [a] -> t b -> t (Maybe (a, b))
forall (t :: * -> *) a b c.
Traversable t =>
(a -> b -> c) -> [a] -> t b -> t (Maybe c)
zipWithMay (,)

-- | Zip a list with any `Traversable`, maintaining the shape of the latter.
--
-- If the list is too short, throw an error with a custom error string.
--
-- >>> zipNote "oops" [1, 2] (Just ())
-- Just (1,())
--
-- >>> zipNote "oops" [] (Just ())
-- Just (*** Exception: oops
-- CallStack (from HasCallStack):
--   zipNote, called at ...
--
-- >>> zipNote "oops" [] Nothing
-- Nothing
zipNote
  :: (HasCallStack, Traversable t) => String -> [a] -> t b -> t (a, b)
zipNote :: forall (t :: * -> *) a b.
(HasCallStack, Traversable t) =>
String -> [a] -> t b -> t (a, b)
zipNote String
errStr = (HasCallStack => [a] -> t b -> t (a, b)) -> [a] -> t b -> t (a, b)
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => [a] -> t b -> t (a, b))
 -> [a] -> t b -> t (a, b))
-> (HasCallStack => [a] -> t b -> t (a, b))
-> [a]
-> t b
-> t (a, b)
forall a b. (a -> b) -> a -> b
$ String -> (a -> b -> (a, b)) -> [a] -> t b -> t (a, b)
forall (t :: * -> *) a b c.
(HasCallStack, Traversable t) =>
String -> (a -> b -> c) -> [a] -> t b -> t c
zipWithNote String
errStr (,)

-- | Use a given function to zip a list with any `Traversable`, maintaining the
-- shape of the latter.
--
-- If the list is too short, pass `Just` values until it runs out, and then
-- `Nothing`s.
--
-- >>> zipWith (maybe id (+)) [1, 2] (Just 10)
-- Just 11
--
-- >>> zipWith (maybe id (+)) [] (Just 10)
-- Just 10
--
-- >>> zipWith (maybe id (+)) [] Nothing
-- Nothing
zipWith :: Traversable t => (Maybe a -> b -> c) -> [a] -> t b -> t c
zipWith :: forall (t :: * -> *) a b c.
Traversable t =>
(Maybe a -> b -> c) -> [a] -> t b -> t c
zipWith Maybe a -> b -> c
f = ([a], t c) -> t c
forall a b. (a, b) -> b
snd (([a], t c) -> t c)
-> ([a] -> t b -> ([a], t c)) -> [a] -> t b -> t c
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
.: ([a] -> b -> ([a], c)) -> [a] -> t b -> ([a], t c)
forall (t :: * -> *) s a b.
Traversable t =>
(s -> a -> (s, b)) -> s -> t a -> (s, t b)
mapAccumL
  (\[a]
as b
b -> case [a]
as of
    []        -> ([], Maybe a -> b -> c
f Maybe a
forall a. Maybe a
Nothing b
b)
    (a
a : [a]
as') -> ([a]
as', Maybe a -> b -> c
f (a -> Maybe a
forall a. a -> Maybe a
Just a
a) b
b)
  )

-- | Use a given function to zip a list with any `Traversable`, maintaining the
-- shape of the latter.
--
-- If the list is too short, throw an error.
--
-- >>> zipWithErr (+) [1, 2] (Just 10)
-- Just 11
--
-- >>> zipWithErr (+) [] (Just 10)
-- Just *** Exception: zipWithErr: list too short
-- CallStack (from HasCallStack):
--   zipWithErr, called at ...
--
-- >>> zipWithErr (+) [] Nothing
-- Nothing
zipWithErr
  :: (HasCallStack, Traversable t) => (a -> b -> c) -> [a] -> t b -> t c
zipWithErr :: forall (t :: * -> *) a b c.
(HasCallStack, Traversable t) =>
(a -> b -> c) -> [a] -> t b -> t c
zipWithErr = (HasCallStack => (a -> b -> c) -> [a] -> t b -> t c)
-> (a -> b -> c) -> [a] -> t b -> t c
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => (a -> b -> c) -> [a] -> t b -> t c)
 -> (a -> b -> c) -> [a] -> t b -> t c)
-> (HasCallStack => (a -> b -> c) -> [a] -> t b -> t c)
-> (a -> b -> c)
-> [a]
-> t b
-> t c
forall a b. (a -> b) -> a -> b
$ String -> (a -> b -> c) -> [a] -> t b -> t c
forall (t :: * -> *) a b c.
(HasCallStack, Traversable t) =>
String -> (a -> b -> c) -> [a] -> t b -> t c
zipWithNote String
"zipWithErr: list too short"


-- | Use a given function to zip an `Infinite` list with any `Traversable`,
-- maintaining the shape of the latter.
--
-- >>> :set -XPostfixOperators
-- >>> import Data.List.Infinite ((...))
--
-- >>> zipWithInf (+) (1...) (Just 10)
-- Just 11
--
-- >>> zipWithInf (+) (1...) Nothing
-- Nothing
zipWithInf
  :: Traversable t => (a -> b -> c) -> Infinite a -> t b -> t c
zipWithInf :: forall (t :: * -> *) a b c.
Traversable t =>
(a -> b -> c) -> Infinite a -> t b -> t c
zipWithInf a -> b -> c
f = (Infinite a, t c) -> t c
forall a b. (a, b) -> b
snd ((Infinite a, t c) -> t c)
-> (Infinite a -> t b -> (Infinite a, t c))
-> Infinite a
-> t b
-> t c
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
.: (Infinite a -> b -> (Infinite a, c))
-> Infinite a -> t b -> (Infinite a, t c)
forall (t :: * -> *) s a b.
Traversable t =>
(s -> a -> (s, b)) -> s -> t a -> (s, t b)
mapAccumL (\(a
a :< Infinite a
as) b
b -> (Infinite a
as, a -> b -> c
f a
a b
b))
-- When infinite-list 0.1.2 is released, this is heteroZipWith. We could use a
-- lazy pattern match here to allow more undefinedness. But that doesn't match
-- that function, and it doesn't match how that package typically does laziness.

-- | Use a given function to zip a list with any `Traversable`, maintaining the
-- shape of the latter.
--
-- If the list is too short, start producing `Nothing`s. You can use `sequence`
-- to get a @`Maybe` (t c)@; but note that this must walk the whole
-- `Traversable` before it can produce a `Just`.
--
-- >>> zipWithMay (+) [1, 2] (Just 10)
-- Just (Just 11)
--
-- >>> zipWithMay (+) [] (Just 10)
-- Just Nothing
--
-- >>> zipWithMay (+) [] Nothing
-- Nothing
zipWithMay :: Traversable t => (a -> b -> c) -> [a] -> t b -> t (Maybe c)
zipWithMay :: forall (t :: * -> *) a b c.
Traversable t =>
(a -> b -> c) -> [a] -> t b -> t (Maybe c)
zipWithMay a -> b -> c
f = (Maybe a -> b -> Maybe c) -> [a] -> t b -> t (Maybe c)
forall (t :: * -> *) a b c.
Traversable t =>
(Maybe a -> b -> c) -> [a] -> t b -> t c
zipWith (\Maybe a
ma b
b -> a -> b -> c
f (a -> b -> c) -> Maybe a -> Maybe (b -> c)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe a
ma Maybe (b -> c) -> Maybe b -> Maybe c
forall a b. Maybe (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> b -> Maybe b
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure b
b)

-- | Use a given function to zip a list with any `Traversable`, maintaining the
-- shape of the latter.
--
-- If the list is too short, throw an error with a custom error string.
--
-- >>> zipWithNote "oops" (+) [1, 2] (Just 10)
-- Just 11
--
-- >>> zipWithNote "oops" (+) [] (Just 10)
-- Just *** Exception: oops
-- CallStack (from HasCallStack):
--   zipWithNote, called at ...
--
-- >>> zipWithNote "oops" (+) [] Nothing
-- Nothing
zipWithNote
  :: (HasCallStack, Traversable t)
  => String -> (a -> b -> c) -> [a] -> t b -> t c
zipWithNote :: forall (t :: * -> *) a b c.
(HasCallStack, Traversable t) =>
String -> (a -> b -> c) -> [a] -> t b -> t c
zipWithNote String
errStr a -> b -> c
f =
  (HasCallStack => [a] -> t b -> t c) -> [a] -> t b -> t c
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => [a] -> t b -> t c) -> [a] -> t b -> t c)
-> (HasCallStack => [a] -> t b -> t c) -> [a] -> t b -> t c
forall a b. (a -> b) -> a -> b
$ (Maybe a -> b -> c) -> [a] -> t b -> t c
forall (t :: * -> *) a b c.
Traversable t =>
(Maybe a -> b -> c) -> [a] -> t b -> t c
zipWith ((Maybe a -> b -> c) -> [a] -> t b -> t c)
-> (Maybe a -> b -> c) -> [a] -> t b -> t c
forall a b. (a -> b) -> a -> b
$ \Maybe a
ma b
b -> a -> b -> c
f (a -> Maybe a -> a
forall a. a -> Maybe a -> a
fromMaybe (String -> a
forall a. HasCallStack => String -> a
error String
errStr) Maybe a
ma) b
b

-- $enumeration
--
-- If all you want is to number off elements starting from @0@, these functions
-- are convenient.

-- | Pair any `Traversable` with the `Int`s starting from @0@.
--
-- >>> enumerate (Just ())
-- Just (0,())
--
-- >>> enumerate "abc"
-- [(0,'a'),(1,'b'),(2,'c')]
enumerate :: Traversable t => t a -> t (Int, a)
enumerate :: forall (t :: * -> *) a. Traversable t => t a -> t (Int, a)
enumerate = [Int] -> t a -> t (Int, a)
forall (t :: * -> *) a b.
(HasCallStack, Traversable t) =>
[a] -> t b -> t (a, b)
zipErr [Int
0..]

-- | Use a given function to pair any `Traversable` with the `Int`s starting
-- from @0@.
--
-- >>> enumerateWith (+) (Just 3)
-- Just 3
--
-- >>> enumerateWith replicate "abc"
-- ["","b","cc"]
enumerateWith :: Traversable t => (Int -> a -> b) -> t a -> t b
enumerateWith :: forall (t :: * -> *) a b.
Traversable t =>
(Int -> a -> b) -> t a -> t b
enumerateWith Int -> a -> b
f = (Int -> a -> b) -> [Int] -> t a -> t b
forall (t :: * -> *) a b c.
(HasCallStack, Traversable t) =>
(a -> b -> c) -> [a] -> t b -> t c
zipWithErr Int -> a -> b
f [Int
0..]

-- $holes
--
-- The "holes" in a `Traversable` structure are the positions that hold
-- traversable elements. For example, if you call `Foldable.toList`, the result
-- contains all the values that were in holes; if you `fmap`, the values in
-- holes get updated, while everything else stays fixed.
--
-- We can use `Compose` to combine the holes of two `Traversable`s. If we have
-- @xs :: f (g a)@ where @f@ and @g@ are both `Traversable`, then the holes of
-- @xs@ are given by the @`Traversable` f@ instance, and have type @g a@. The
-- holes of @v`Compose` xs :: `Compose` f g a@ are the holes of the @g@s that
-- are themselves in the holes of the @f@, and have type @a@.
--
-- For example, the holes in these data structures are bolded:
--
-- @
-- -- Compose [] with `Maybe`:
--         [__Just 1__, __Nothing__, __Just 2__, __Nothing__] :: [Maybe Int]
-- Compose [Just __1__, Nothing, Just __2__, Nothing] :: Compose [] Maybe Int
--
-- -- Compose ((,) a) with []:
--         (True, __[1, 2, 3]__) :: (Bool, [Int])
-- Compose (True, [__1__, __2__, __3__]) :: Compose ((,) Bool) [] Int
--
-- -- Compose (`Either` a) with []:
--          Left  [1, 2]  :: Either [Int] [Int]
--          Right __[1, 2]__  :: Either [Int] [Int]
-- Compose (Left  [1, 2]) :: Compose (Either [Int]) [] Int
-- Compose (Right [__1__, __2__]) :: Compose (Either [Int]) [] Int
--
-- -- Nested `Compose`:
--          Compose [Just __(Just 1)__, Just __Nothing__, Nothing]  :: Compose [] Maybe (Maybe Int)
-- Compose (Compose [Just (Just __1__), Just Nothing, Nothing]) :: Compose (Compose [] Maybe) Maybe Int
-- @
--
-- When zipping, holes are relevant because every hole gets paired with exactly
-- one list element. You can use `setHoles` and `unsetHoles` to control the
-- pairing-off.
--
-- With @`setHoles` f x@, @f@ gets called once for every hole in @x@; and every
-- hole returned by every call to @f@ will be a hole. So if @f@ returns...
--
-- * ... @`Maybe` a@, then only the `Just` values will be holes.
-- * ... @`Either` a b@, then only the `Right` values will be holes.
-- * ... @(a, b)@, then only the `snd` values will be holes.
-- * ... @[a]@, then every list element will be a hole.
--
-- And @`unsetHoles` g x@ acts as an inverse; @g@ gets called on parts of @x@
-- that may have any number of holes, and whatever @g@ returns will become a
-- single hole in its own right.
--
-- >>> let xs = [Just 10, Nothing, Just 20, Nothing]
-- >>> enumerate xs
-- [(0,Just 10),(1,Nothing),(2,Just 20),(3,Nothing)]
-- >>> -- Only enumerate the `Just` values:
-- >>> unsetHoles id $ enumerate $ setHoles id xs
-- [Just (0,10),Nothing,Just (1,20),Nothing]
--
-- >>> let xs = [11,22..66]
-- >>> zipWithErr (+) [1..] xs
-- [12,24,36,48,60,72]
-- >>> -- Only add to the even numbers:
-- >>> :{
--   unsetHoles (either id id)
--     $ zipWithErr (+) [1..]
--     $ setHoles (\x -> if even x then Right x else Left x)
--     $ xs
-- :}
-- [11,23,33,46,55,69]

-- | Set the holes in a `Traversable` by wrapping in `Compose`.
--
-- @
-- setHoles f x = `Compose` (`fmap` f x)
-- @
setHoles :: Functor f => (a -> g b) -> f a -> Compose f g b
setHoles :: forall (f :: * -> *) a (g :: * -> *) b.
Functor f =>
(a -> g b) -> f a -> Compose f g b
setHoles a -> g b
f = f (g b) -> Compose f g b
forall {k} {k1} (f :: k -> *) (g :: k1 -> k) (a :: k1).
f (g a) -> Compose f g a
Compose (f (g b) -> Compose f g b)
-> (f a -> f (g b)) -> f a -> Compose f g b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> g b) -> f a -> f (g b)
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> g b
f

-- | Simplify the holes in a `Traversable` by unwrapping from `Compose`.
--
-- @
-- unsetHoles f x = `fmap` f (`getCompose` x)
-- @
unsetHoles :: Functor f => (g a -> b) -> Compose f g a -> f b
unsetHoles :: forall (f :: * -> *) (g :: * -> *) a b.
Functor f =>
(g a -> b) -> Compose f g a -> f b
unsetHoles g a -> b
f = (g a -> b) -> f (g a) -> f b
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap g a -> b
f (f (g a) -> f b)
-> (Compose f g a -> f (g a)) -> Compose f g a -> f b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Compose f g a -> f (g a)
forall {k1} {k2} (f :: k1 -> *) (g :: k2 -> k1) (a :: k2).
Compose f g a -> f (g a)
getCompose

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
.: :: forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = ((b -> c) -> b -> d) -> (a -> b -> c) -> a -> b -> d
forall b c a. (b -> c) -> (a -> b) -> a -> c
(.) (((b -> c) -> b -> d) -> (a -> b -> c) -> a -> b -> d)
-> ((c -> d) -> (b -> c) -> b -> d)
-> (c -> d)
-> (a -> b -> c)
-> a
-> b
-> d
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (c -> d) -> (b -> c) -> b -> d
forall b c a. (b -> c) -> (a -> b) -> a -> c
(.)

-- == __Boring list of more examples__
--

-- $traversable
--
-- A `Traversable` structure @x :: t a@ can be decomposed into
--
-- * its __spine:__ @`const` () `<$>` x :: t ()@
-- * its __element list:__ @`Data.Foldable.toList` x :: [a]@
--
-- It can then be reconstructed from these. Or, given another list @[b]@ of the
-- same length, we can use the original spine to create a @t b@ with these new
-- elements, of the same shape as the original.
--
-- @`zipWithErr` `const`@ is the function that reconstructs it. That is,
--
-- @
-- `zipWithErr` `const` (elementList x) (spine x) === x
-- @
--
-- allows you to exactly recreate the original structure, or to replace its
-- element list by providing a different one.