MapWith (empty) → 0.1.0.0
raw patch · 10 files changed
+574/−0 lines, 10 filesdep +basesetup-changed
Dependencies added: base
Files
- ChangeLog.md +4/−0
- LICENSE +29/−0
- MapWith.cabal +83/−0
- Setup.hs +2/−0
- perf/IndEnd.hs +11/−0
- perf/IndEndBaseline.hs +13/−0
- perf/PrevNext.hs +8/−0
- perf/PrevNextBaseline.hs +16/−0
- src/MapWith.hs +351/−0
- test/MapWithTest.hs +57/−0
+ ChangeLog.md view
@@ -0,0 +1,4 @@+# Revision history for MapWith + +## 0.1.0.0 -- 2020-06-24 +* First release
+ LICENSE view
@@ -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.
+ MapWith.cabal view
@@ -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 +
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain
+ perf/IndEnd.hs view
@@ -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
+ perf/IndEndBaseline.hs view
@@ -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)
+ perf/PrevNext.hs view
@@ -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
+ perf/PrevNextBaseline.hs view
@@ -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) + +
+ src/MapWith.hs view
@@ -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 (,,)
+ test/MapWithTest.hs view
@@ -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 +