MapWith

mapWith: like fmap, but with additional parameters (isFirst, isLast, etc).

https://github.com/davjam/MapWith#readme

Stackage Nightly 2020-12-04:0.2.0.0
Latest on Hackage:0.2.0.0

See all snapshots MapWith appears in

BSD-3-Clause licensed by David James
Maintained by dj112358@outlook.com
This version can be pinned in stack with:MapWith-0.2.0.0@sha256:6e8ccc02bf6684b26367c39b7c4f275da1dd4a0a36fc2c0fef469d58c485dfae,4151

Module documentation for 0.2.0.0

Depends on 1 package(full list with versions):

MapWith Stackage version Hackage version build status

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 all Traversable 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 by CurryTF as an application of a single value 7, but by Data.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.
  • Improved documentation including examples and benchmark stats.
  • Also tested in GHC 8.10.1