MapWith
mapWith: like fmap, but with additional parameters (isFirst, isLast, etc).
https://github.com/davjam/MapWith#readme
LTS Haskell 18.28: | 0.2.0.0 |
Stackage Nightly 2021-06-14: | 0.2.0.0 |
Latest on Hackage: | 0.2.0.0 |
MapWith-0.2.0.0@sha256:6e8ccc02bf6684b26367c39b7c4f275da1dd4a0a36fc2c0fef469d58c485dfae,4151
MapWith

mapWith
: like fmap
, but can “inject” additional parameters such as whether first (or last) element, etc.
Background
I often want to map over a list, but do something slightly different with the first or last element.
For a long time I used markbounds, but also wanted something that:
- works on structures other than lists (
mapWith
works on allTraversable
types); - can provide additional types of parameter (not just first/last), such as:
- index from start/end;
- the previous/next element; and
- makes it easy to create new types of parameter to provide; and
- can provide any number of separate parameters to a function (not just a 3-tuple).
So, after only 2 years, I built a small library to do all of these.
Examples
Passing a “standard combination” of isFirst and isLast parameters:
let g x f l = [star f, x, star l]; star b = if b then '*' else ' '
in withFirstLast g "fred"
["*f ", " r ", " e ", " d*"]
Passing a custom combination of different types of parameter (the index from the start, whether it’s the last element, and elements from another list applied from the right):
let g x n l e = concat [[x], show n, if l then "*" else "-", e]
in mapWith (g ^-> eltIx & isLast <-^ eltFrom ["x","yy","z","zzzz","y"]) "fred"
["f0-zzzz","r1-z","e2-yy","d3*x"]
More examples are here.
Questions/Doubts
Note that this is my first library and my first use of cabal, so I’ve probably done some dumb things.
Some things I wonder:
- Doesn’t this already exist? (It feels like it should!)
- Should I name it
Data.Traversable.MapWith
? Or are such names “reserved” for “official” libraries, or something? Would this name impact my own file/directory structures?
Future Work
Areas for potential improvement in later releases:
-
Performance investigations and hopefully improvements, in particular:
- fusion for eltFrom Injectors (unlikely, given the reasons it’s not possible for zipWith, but we’ll see).
- enhancements for “stateful” “from the right” Injectors (unlikely, given this).
-
CurryTF: avoid tuples? (The tuple
(7, ())
is interpreted byCurryTF
as an application of a single value7
, but byData.Tuple.Curry
as two values:7
and()
, which I think is slightly more confusing than it needs to be.)
Changes
Revision history for MapWith
0.1.0.0 – 2020-06-24
- First release
0.2.0.0 – 2020-08-25
- Significant performance improvements (including fusion)
- New Features:
- An Injector can inject multiple values (for example adj2Elts)
- New Injectors:
- evenElt
- foldlElts and foldl1Elts
- adj2Elts
- New utility functions:
- withFirst
- withLast
- Breaking Changes:
- eltFrom (& similar) now consume a List, not a Foldable. (They never used any features of Foldables, other than converting them to a list).
- Injector functions have two changes. To convert Injectors, change
(\a s -> ... (i, s'))
to(\a s -> ... (s', app1 i))
:- the order of the output pair is reversed for consistancy with state transformers,
mapAccumL
, etc. It’s now(new-state, injection-values)
. - the injector types and values now need to be instances of CurryTF.
- the order of the output pair is reversed for consistancy with state transformers,
- Improved documentation including examples and benchmark stats.
- Also tested in GHC 8.10.1