diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,4 @@
+# Revision history for MapWith
+
+## 0.1.0.0 -- 2020-06-24
+* First release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2020, davjam
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MapWith.cabal b/MapWith.cabal
new file mode 100644
--- /dev/null
+++ b/MapWith.cabal
@@ -0,0 +1,83 @@
+name:                MapWith
+version:             0.1.0.0
+
+synopsis:            mapWith: like fmap, but with additional arguments (isFirst, isLast, etc).
+category:            Combinators
+maintainer:          dj112358@outlook.com
+
+description:         fmap over Traversables (including lists), but pass additional parameters to the map function, such as
+                     isFirst, isLast, prevElt, nextElt, index from start or end, custom params.
+                     For examples see https://github.com/davjam/MapWith/blob/master/doc/examples.hs
+
+license:             BSD3
+license-file:        LICENSE
+author:              David James
+copyright:           (c) David James, 2020.
+
+build-type:          Simple
+tested-with:         GHC == 8.0.2, GHC == 8.4.3, GHC == 8.8.3
+extra-source-files:  ChangeLog.md
+
+cabal-version:       >=1.10
+
+source-repository head
+  type:              git
+  location:          https://github.com/davjam/MapWith.git
+
+library
+  exposed-modules:     MapWith
+  hs-source-dirs:      src
+  ghc-options:         -Wall
+  other-extensions:    ExistentialQuantification
+  default-language:    Haskell2010
+  build-depends:       base >= 4.9.1 && < 4.15
+  --I know it works on 4.11.1. I believe suspect it will work on 4.14, based on the documentation (e.g. https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html#v:mapAccumL)
+
+Test-Suite test-MapWith
+  main-is:             MapWithTest.hs
+  Other-modules:       MapWith
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      src test
+  default-language:    Haskell2010
+  build-depends:       base
+
+
+--cabal bench perf-prev-next-baseline --benchmark-options="+RTS -P -hd"
+--hp2ps -e8in -c perf-prev-next-baseline.hp
+--see https://stackoverflow.com/questions/22942194/profiling-an-executable-with-cabal
+benchmark perf-prev-next-baseline
+  main-is:             PrevNextBaseline.hs
+  hs-source-dirs:      perf
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010        
+  build-depends:       base
+
+--cabal bench perf-ind-end-baseline --benchmark-options="+RTS -P -hd"
+--hp2ps -e8in -c perf-ind-end-baseline.hp
+benchmark perf-ind-end-baseline
+  main-is:             IndEndBaseline.hs
+  hs-source-dirs:      perf
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  build-depends:       base
+
+--cabal bench perf-prev-next --benchmark-options="+RTS -P -hd"
+--hp2ps -e8in -c perf-prev-next.hp
+benchmark perf-prev-next
+  main-is:             PrevNext.hs
+  Other-modules:       MapWith
+  hs-source-dirs:      src perf
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  build-depends:       base
+
+--cabal bench perf-ind-end --benchmark-options="+RTS -P -hd"
+--hp2ps -e8in -c perf-ind-end.hp
+benchmark perf-ind-end
+  main-is:             IndEnd.hs
+  Other-modules:       MapWith
+  hs-source-dirs:      src perf
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  build-depends:       base
+ 
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/perf/IndEnd.hs b/perf/IndEnd.hs
new file mode 100644
--- /dev/null
+++ b/perf/IndEnd.hs
@@ -0,0 +1,11 @@
+
+
+import MapWith
+
+main = do
+  print $ sum $ withEndIx xxx [1..1000000]
+  where
+  xxx n nEndInd = n + nEndInd
+
+withEndIx :: Traversable t => (a -> Int -> b) -> t a -> t b
+withEndIx f = mapWith $ f <-^ eltIx
diff --git a/perf/IndEndBaseline.hs b/perf/IndEndBaseline.hs
new file mode 100644
--- /dev/null
+++ b/perf/IndEndBaseline.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE BangPatterns #-}
+
+import Data.Traversable (mapAccumR)
+
+main = do
+  print $ sum $ withEndIx xxx [1..1000000]
+  where
+  xxx n nEndInd = n + nEndInd
+
+withEndIx :: Traversable t => (a -> Int -> b) -> t a -> t b
+withEndIx f !t = snd $ mapAccumR acc 0 t
+  where
+  acc !i a = (i+1, f a i)
diff --git a/perf/PrevNext.hs b/perf/PrevNext.hs
new file mode 100644
--- /dev/null
+++ b/perf/PrevNext.hs
@@ -0,0 +1,8 @@
+import MapWith
+import Data.Maybe (fromMaybe)
+
+main = do
+  print $ sum $ withPrevNext xxx [1..1000000]
+  where
+  xxx :: Int -> Maybe Int -> Maybe Int -> Int
+  xxx n prevMay nextMay = n + fromMaybe 0 prevMay + fromMaybe 0 nextMay
diff --git a/perf/PrevNextBaseline.hs b/perf/PrevNextBaseline.hs
new file mode 100644
--- /dev/null
+++ b/perf/PrevNextBaseline.hs
@@ -0,0 +1,16 @@
+import Data.Traversable (mapAccumL, mapAccumR)
+import Data.Maybe (fromMaybe)
+
+main = do
+  print $ sum $ withPrevNext xxx [1..1000000]
+  where
+  xxx :: Int -> Maybe Int -> Maybe Int -> Int
+  xxx n prevMay nextMay = n + fromMaybe 0 prevMay + fromMaybe 0 nextMay
+
+withPrevNext :: Traversable t => (a -> Maybe a -> Maybe a -> b) -> t a -> t b
+withPrevNext f = snd . mapAccumR accR Nothing . snd . mapAccumL accL Nothing
+  where
+  accL prevMay a = (Just a, (a, f a prevMay))
+  accR nextMay (a, fap) = (Just a, fap nextMay)
+  
+  
diff --git a/src/MapWith.hs b/src/MapWith.hs
new file mode 100644
--- /dev/null
+++ b/src/MapWith.hs
@@ -0,0 +1,351 @@
+{-# OPTIONS_GHC -Wall #-}
+{-# LANGUAGE ExistentialQuantification #-}
+
+-- |
+-- Module      : MapWith
+-- Description : blah
+-- Copyright   : (c) David James, 2020
+-- License     : BSD3
+-- Stability   : Experimental
+--
+-- Provides 'fmap'-like functionality, but can also "inject" additional parameters to the mapping function, such as:
+--
+-- * whether the first / last item
+-- * the previous / next item
+-- * the index from the start / end
+
+module MapWith
+  (
+  -- * Type Names
+  -- $TypeNames
+
+  -- * Pre-Packaged Maps
+  -- $PrePackagedMaps
+    withFirstLast
+  , andFirstLast
+  , withPrevNext
+  , andPrevNext
+
+  -- * Custom Maps
+  -- $CustomMaps
+  , mapWith
+  , mapWithM
+  , mapWithM_
+  , foldMapWith
+  , InjectedFn
+  , Injectable(..)
+  
+
+  -- * Predefined Injectors
+  -- $PredefinedInjectors
+  , isLim
+  , adjElt
+  , eltIx
+  , eltFrom
+  , eltFromMay
+  , eltFromDef
+  
+  -- ** Pre-Combined Injectors
+  -- $PrecombinedInjectors
+  , isFirst
+  , isLast
+  , prevElt
+  , nextElt
+
+  -- * Custom Injectors
+  , Injector(..)
+  )
+where
+
+import Data.Foldable (fold, toList)
+import Data.Traversable (mapAccumL, mapAccumR)
+import Data.Function ((&))
+import Control.Exception (assert)
+
+-- $TypeNames
+-- These 'names' are used for types and variables throughout:
+--
+-- [@t@]: the 'Traversable' we're mapping over
+-- [@a@]: the value in the input 'Traversable'
+-- [@b@]: the result in the output 'Traversable'
+-- [@i@]: an output from an 'Injector', injected into a map function
+-- [@s@]: the internal state in an 'Injector'
+
+--XXXX I'd like to add separate comments for each argument, but that's not supported to GHC 8.6 https://github.com/haskell/haddock/issues/836#issuecomment-391402361
+data Injector a i = forall s. Injector (a -> s -> (i, s)) s -- ^the first argument is a generate function, the second argument is the initial state.
+
+-- ^ Injectors have an initial state and a generate function.
+--
+--  For each item in the 'Traversable', the generate function can use both:
+--
+--  - the item from the 'Traversable', and
+--  - the current state
+--
+--  to determine both:
+--
+--  - the injection value, and
+--  - the new state.
+--
+--  The first value to inject is determined by a first call to the generate function.
+--  The first call to the generate function is with the first (if combined with '^->') or last (if combined with '<-^') item from the 'Traversable' and the initial state.
+--
+--  For example:
+--
+--  >>> funnyNext a s = (a + s, a + 1)
+--  >>> funnyInjector = Injector funnyNext 17
+--  >>> mapWith ((\_ i -> i) ^-> funnyInjector) [4,8,3]
+--  [21,13,12]
+--
+--  +-------+---------------+------+---------------+-----------------+
+--  + Call  + Initial State + Item + Injection     + New State       +
+--  +=======+===============+======+===============+=================+
+--  + 1     + 17            + 4    + 17+4=__21__   + 4+1=5           +
+--  +-------+---------------+------+---------------+-----------------+
+--  + 2     + 5             + 8    + 5+8=__13__    + 8+1=9           +
+--  +-------+---------------+------+---------------+-----------------+
+--  + 3     + 9             + 3    + 9+3=__12__    + 3+1=4 (ignored) |
+--  +-------+---------------+------+---------------+-----------------+
+--
+--  >>> mapWith ((\_ i -> i) <-^ funnyInjector) [4,8,3]
+--  [13,12,20]
+--
+--  +-------+---------------+------+---------------+-----------------+
+--  + Call  + Initial State + Item + Injection     + New State       +
+--  +=======+===============+======+===============+=================+
+--  + 1     + 17            + 3    + 17+3=__20__   + 3+1=4           +
+--  +-------+---------------+------+---------------+-----------------+
+--  + 2     + 4             + 8    + 4+8=__12__    + 8+1=9           +
+--  +-------+---------------+------+---------------+-----------------+
+--  + 3     + 9             + 4    + 9+4=__13__    + 4+1=5 (ignored) |
+--  +-------+---------------+------+---------------+-----------------+
+--
+--  More usefully, this would allow for e.g. the prior two elements:
+--
+--  > prev2Inj = Injector (\x i@(prev1May, prev2May) -> (i, (Just x, prev1May))) (Nothing, Nothing)
+--
+-- or random values, etc.
+
+injPair :: Injector a i1 -> Injector a i2 -> Injector a (i1, i2)
+injPair (Injector n1 z1) (Injector n2 z2) = Injector nxt (z1, z2)
+  where
+  nxt a ~(s1, s2) = let (i1, s1') = n1 a s1       -- !! NOTE THE ~ !! It allows "constant" injectors (e.g. isLim), and hence e.g. andFirstLast to work on infinite lists.
+                        (i2, s2') = n2 a s2
+                    in ((i1, i2), (s1', s2'))
+
+-- $PredefinedInjectors
+-- Use these (or custom 'Injector's) to create 'InjectedFn's that can be used with 'mapWith'
+
+isLim :: Injector a Bool
+isLim = Injector (\_ i -> (i, False)) True
+-- ^ inject 'True' if the item is at the limit:
+--
+-- - from the left: if it's the first item
+-- - from the right: if it's the last item
+--
+-- else inject False.
+
+eltIx :: Integral i => Injector a i
+eltIx = Injector (\_ i -> (i, i+1)) 0
+-- ^ inject the item index:
+--
+-- - from the left: the first item is 0, the second 1, etc.
+-- - from the right: the last item is 0, the penultimate 1, etc.
+
+eltFrom :: Foldable f
+      => f i          -- ^ The elements to inject. There must be enough elements.
+      -> Injector a i
+eltFrom f = Injector (\_ s -> assert (not $ null s) (head s, tail s)) (toList f)
+-- ^ Inject each given element in turn:
+--
+-- - from the left: the first element will be injected for the first item in the 'Traversable'.
+-- - from the right: the first element will be injected for the last item in the 'Traversable'.
+--
+-- As a result of laziness, it is not always an error if there are not enough elements, for example:
+--
+-- >>> drop 1 $ mapWith ((\_ i -> i) <-^ eltFrom [8,2]) "abc"
+-- [2,8]
+
+eltFromMay :: Foldable f => f i -> Injector a (Maybe i)
+eltFromMay f = Injector (\_ s -> case s of []      -> (Nothing, [])
+                                           (sh:st) -> (Just sh, st))
+                         (toList f)
+-- ^ a safe version of `eltFrom`. Injects 'Just' each given element in turn, or 'Nothing' after they've been exhausted.
+
+eltFromDef :: Foldable f => i -> f i -> Injector a i
+eltFromDef def f = Injector (\_ s -> case s of []      -> (def, [])
+                                               (sh:st) -> (sh, st))
+                            (toList f)
+-- ^ a safe version of `eltFrom`. Injects each given element in turn, or the default after they've been exhausted.
+
+adjElt :: Injector a (Maybe a)
+adjElt = Injector (\a prevMay -> (prevMay, Just a)) Nothing
+-- ^ inject 'Just' the adjacent item:
+--
+-- - from the left: the previous item, except for the first item
+-- - from the right: the next item, except for the last item. (The "previous from the right" is the "next".)
+--
+-- inject 'Nothing' if there is no adjacent item (i.e. for the first / last).
+
+-- $CustomMaps
+--
+-- In general, a map function will take one parameter from the 'Traversable', then one each from any number of 'Injector's. For example:
+--
+-- >>> mapFn w x y z = (w, x, y, z)
+-- >>> injectedFn = mapFn <-^ isLim ^-> eltIx <-^ eltFrom [8,2,7,1]
+-- >>> mapWith injectedFn "abc"
+-- [('a',False,0,7),('b',False,1,2),('c',True,2,8)]
+--
+-- Where:
+--
+-- - @mapFn@: a function that maps over a 'Traversable', but requires additional parameters
+-- - @injectedFn@: represents the combination of @mapFn@ with three injectors that provide the required parameters:
+--
+--     - @'<-^' 'isLim'@: injects True if this is the limit, from the right (i.e. the last item).
+--     - @'^->' 'eltIx'@: inject the index, from the left
+--     - @'<-^' 'eltFrom' [8,2,7,1]@: inject elements from this list, from the right.
+--
+-- 'mapWith' then maps the @mapFn@ over the 'Traversable', with the following parameters:
+--
+-- +------+--------+---------+---------+---------+
+-- | Call | w      | x       | y       | z       |
+-- +======+========+=========+=========+=========+
+-- |    1 | \'a\'  | 'False' | 0       | 7       |
+-- +------+--------+---------+---------+---------+
+-- |    2 | \'b\'  | 'False' | 1       | 2       |
+-- +------+--------+---------+---------+---------+
+-- |    3 | \'c\'  | 'True'  | 2       | 8       |
+-- +------+--------+---------+---------+---------+
+
+mapWith :: Traversable t
+        => InjectedFn a b
+        -> t a
+        -> t b
+mapWith (InjectedFnL  f (Injector gen z)) = snd . mapAccumL acc z
+  where acc s a = let (i, s') = gen a s in (s', f a i)
+mapWith (InjectedFnR  f (Injector gen z)) = snd . mapAccumR acc z
+  where acc s a = let (i, s') = gen a s in (s', f a i)
+mapWith (InjectedFnLR f (Injector genL zL) (Injector genR zR)) = snd . mapAccumR accR zR . snd . mapAccumL accL zL
+  where accL s  a       = let (i, s') = genL a s in (s', (a, f a i))
+        accR s (a, fal) = let (i, s') = genR a s in (s',     fal i )
+{-
+--This may be clever, but actually slower, and the generation of the (a,f) tuples above doesn't seem to add much time/heap.
+mapWith (InjectedFnLR f (Injector genL zL) (Injector genR zR)) = snd . mapAccumR accR zR . snd . mapAccumL accL zL
+  where accL sl a   = let (l, sl') = genL a sl in (sl', \sr -> let (r, sr') = genR a sr in (sr', f a l r))
+        accR sr fsr = fsr sr
+-}
+-- ^ maps an 'InjectedFn' over a 'Traversable' type @t@, turning a @t a@ into a @t b@ and preserving the structure of @t@.
+--
+-- Parameters (as defined in the 'InjectedFn') are passed to a map function (embedded in the 'InjectedFn'), in addition to the elements of the 'Traversable'.
+
+mapWithM :: (Traversable t, Monad m) => InjectedFn a (m b) -> t a -> m (t b)
+mapWithM f = sequence . mapWith f
+-- ^ like 'mapM', but with an 'InjectedFn'.
+
+mapWithM_ :: (Traversable t, Monad m) => InjectedFn a (m b) -> t a -> m ()
+mapWithM_ f = sequence_ . mapWith f
+-- ^ like 'mapM_' (which is like 'mapM' but ignores the results), but with an 'InjectedFn'.
+
+foldMapWith :: (Traversable t, Monoid b) => InjectedFn a b -> t a -> b
+foldMapWith f = fold . mapWith f
+-- ^ like 'foldMap', but with an 'InjectedFn'
+
+data InjectedFn a b
+  = forall l r. InjectedFnLR (a -> l -> r -> b) (Injector a l) (Injector a r)
+  | forall l  . InjectedFnL  (a -> l      -> b) (Injector a l)
+  | forall   r. InjectedFnR  (a      -> r -> b)                (Injector a r)
+
+-- ^ Represents a function from @a@, plus a number of injected values, to @b@.
+--
+-- Used by 'mapWith' (& related), which maps over a 'Traversable', injecting the additional values as it goes.
+--
+-- Constructed by combining  a map function with 'Injector's using the '^->' and '<-^' operators.
+--
+-- The sequence:
+--
+-- @(a -> i1 -> i2 -> ... -> in -> b) /op1/ /inj1/ /op2/ /inj2/ ... /opn/ /injn/@
+--
+-- where:
+--
+-- - each @/op/@ is '^->' or '<-^'; and
+-- - each @/inj/@ is an 'Injector'
+--
+-- produces an @'InjectedFn' a b@, with n injected values.
+
+class Injectable m where
+  -- | Inject "from the left"
+  (^->) :: (m a (i -> b)) -> Injector a i -> InjectedFn a b
+  -- | Inject "from the right"
+  (<-^) :: (m a (i -> b)) -> Injector a i -> InjectedFn a b
+
+-- ^ An 'Injectable' is (recursively) either:
+--
+-- - a function @(a -> i -> b)@; or
+-- - an @InjectedFn a (i -> b)@, created by @'Injectable' /op/ 'Injector'@
+
+infixl 1 ^->
+infixl 1 <-^
+
+instance Injectable (->) where
+  f ^-> itL' = InjectedFnL (\a l   -> f a l) itL'
+  f <-^ itR' = InjectedFnR (\a   r -> f a r)        itR'
+
+instance Injectable InjectedFn where
+  InjectedFnL  f itL     ^-> itL' = InjectedFnL  (\a (l, l')   -> f a l   l') (injPair itL itL')
+  InjectedFnR  f     itR ^-> itL' = InjectedFnLR (\a     l'  r -> f a   r l')          itL'            itR
+  InjectedFnLR f itL itR ^-> itL' = InjectedFnLR (\a (l, l') r -> f a l r l') (injPair itL itL')       itR
+
+  InjectedFnL  f itL     <-^ itR' = InjectedFnLR (\a l     r'  -> f a l   r')          itL                 itR'
+  InjectedFnR  f     itR <-^ itR' = InjectedFnR  (\a   (r, r') -> f a   r r')                 (injPair itR itR')
+  InjectedFnLR f itL itR <-^ itR' = InjectedFnLR (\a l (r, r') -> f a l r r')          itL    (injPair itR itR')
+
+-- $PrecombinedInjectors
+-- These are combinations of '^->' or '<-^' with 'isLim' or 'adjElt'.
+--
+-- They work well with the '&' operator, and can be combined with the '^->' and '<-^' operators e.g.:
+--
+-- prop> mapWith (f & isFirst <-^ eltFrom [9,2]) == mapWith (f ^-> isLim <-^ eltFrom [9,2])
+--
+-- You may find them more memorable or easier to type.
+
+isFirst :: Injectable f => f a (Bool -> b) -> InjectedFn a b
+isFirst f = f ^-> isLim
+-- ^ 'isLim', from the left.
+
+isLast :: Injectable f => f a (Bool -> b) -> InjectedFn a b
+isLast f = f <-^ isLim
+-- ^ 'isLim', from the right.
+
+prevElt :: Injectable f => f a (Maybe a -> b) -> InjectedFn a b
+prevElt f = f ^-> adjElt
+-- ^ 'adjElt', from the left.
+
+nextElt :: Injectable f => f a (Maybe a -> b) -> InjectedFn a b
+nextElt f = f <-^ adjElt
+-- ^ 'adjElt', from the right.
+
+-- $PrePackagedMaps
+-- Some pre-defined maps with commonly used injectors.
+
+withFirstLast :: Traversable t => (a -> Bool -> Bool -> b) -> t a -> t b
+withFirstLast f = mapWith $ f & isFirst & isLast
+-- ^ Maps over a 'Traversable', with additional parameters indicating whether an item is the first or last (or both) in the list.
+--
+-- >>> let f x isFirst isLast = star isFirst ++ x ++ star isLast; star b = if b then "*" else "" in withFirstLast f ["foo", "bar", "baz"]
+-- ["*foo", "bar", "baz*"]
+
+andFirstLast :: Traversable t => t a -> t (a, Bool, Bool)
+andFirstLast = withFirstLast (,,)
+-- ^ > andFirstLast = withFirstLast (,,)
+
+withPrevNext :: Traversable t => (a -> Maybe a -> Maybe a -> b) -> t a -> t b
+withPrevNext f = mapWith $ f & prevElt & nextElt
+-- ^ Maps over a 'Traversable', with additional parameters indicating the previous and next elements.
+--
+-- The second (or third) parameter to the map function is 'Nothing' when called for the first (or last) item, otherwise it's 'Just' the previous (or next) item.
+--
+-- >>> let f x prvMay nxtMay = maybe "*" (cmp x) prvMay ++ x ++ maybe "*" (cmp x) nxtMay; cmp x y = show $ compare x y in withPrevNext f ["foo", "bar", "baz"]
+-- ["*fooGT","LTbarLT","GTbaz*"]
+
+andPrevNext :: Traversable t => t a -> t (a, Maybe a, Maybe a)
+andPrevNext = withPrevNext (,,)
+-- ^ > andPrevNext = withPrevNext (,,)
diff --git a/test/MapWithTest.hs b/test/MapWithTest.hs
new file mode 100644
--- /dev/null
+++ b/test/MapWithTest.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveTraversable #-}
+
+import System.Exit
+import Data.Function ((&))
+import MapWith
+
+testFn0 :: a -> b -> b
+testFn0 _ b = b
+
+testFn :: String -> Bool -> Int -> String
+testFn s True  index = replicate index ' ' ++ s ++ "*"
+testFn s False index = replicate index ' ' ++ s
+
+testFn2 :: String -> Bool -> Int -> Int -> String
+testFn2 s True  index ind2 = replicate index ' ' ++ s ++ (show ind2) ++ "*"
+testFn2 s False index ind2 = replicate index ' ' ++ s ++ (show ind2)
+
+testFnP :: String -> (Bool, Int) -> String
+testFnP s (True , index) = replicate index ' ' ++ s ++ "*"
+testFnP s (False, index) = replicate index ' ' ++ s
+
+r0 = mapWith (testFn0 ^-> isLim) fbb
+r2 = mapWith (testFn  <-^ isLim <-^ eltIx) fbb
+r3 = mapWith (testFn  <-^ isLim ^-> eltIx) fbb
+r4 = mapWith (testFn2 ^-> isLim ^-> eltIx <-^ eltFrom [8,9,10,11]) fbb
+
+fbb = ["foo", "bar", "baz"]
+
+data FunnySet a = FunnySet a a a a a
+  deriving (Eq, Show, Functor, Foldable, Traversable)
+
+tests :: [Bool]
+tests =
+  [
+    mapWith (testFn0 & isFirst) "abc"   == [True,  False, False]
+  , mapWith (testFn0 & isLast) "abc"   == [False, False, True ]
+  , mapWith ((\_ a b c d -> (a,b,c,d)) <-^ isLim ^-> eltIx <-^ eltIx ^-> isLim) "abc"
+                                        == [(False,0,2,True),(False,1,1,False),(True,2,0,False)]
+  , mapWith (testFn0 & prevElt) "abc"  == [Nothing, Just 'a', Just 'b']
+  , mapWith (testFn0 & nextElt) "abc"  == [Just 'b', Just 'c', Nothing]
+  , andFirstLast "abc"                  == [('a',True,False),('b',False,False),('c',False,True)]
+  , take 3 (andFirstLast [1..])         == [(1,True,False),(2,False,False),(3,False,False)]
+  , andFirstLast (FunnySet 8 9 1 2 5)   == FunnySet (8,True,False) (9,False,False) (1,False,False) (2,False,False) (5,False,True)
+  , mapWith (testFn0 <-^ eltFromMay [1,2]) [1,2,3]
+                                        == [Nothing, Just 2, Just 1]
+  , mapWith (testFn0 <-^ eltFromDef 7 [1,2]) [1,2,3]
+                                        == [7, 2, 1]
+
+  ]
+
+main = do
+  if and tests
+  then exitSuccess
+  else exitFailure
+
