packages feed

bimap 0.2.4 → 0.5.0

raw patch · 10 files changed

Files

Data/Bimap.hs view
@@ -1,3 +1,9 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+#if __GLASGOW_HASKELL__ >= 708+{-# LANGUAGE TypeFamilies #-}+#endif+ {-| An implementation of bidirectional maps between values of two key types. A 'Bimap' is essentially a bijection between subsets of@@ -30,12 +36,22 @@     lookupR,     (!),     (!>),+    (!?),+    (!?>),     -- * Construction     empty,     singleton,     -- * Update     insert,     tryInsert,+    adjust,+    adjustR,+    adjustWithKey,+    adjustWithKeyR,+    update,+    updateR,+    updateWithKey,+    updateWithKeyR,     delete,     deleteR,     -- * Min\/Max@@ -67,6 +83,10 @@     elems,     assocs,     fold,+    Data.Bimap.map,+    mapR,+    mapMonotonic,+    mapMonotonicR,     toMap,     toMapR,     -- * Miscellaneous@@ -75,12 +95,24 @@     twisted, ) where -import Data.List (foldl', sort)-import qualified Data.Map as M-import Prelude hiding (lookup, null, filter, pred)-import qualified Prelude as P+import           Control.DeepSeq     (NFData)+import           Control.Monad.Catch +import           Data.Function       (on)+import           Data.List           (foldl', sort)+import qualified Data.Map            as M+import           Data.Maybe          (fromMaybe)+import           Data.Typeable +#if __GLASGOW_HASKELL__ >= 708+import qualified Data.BimapExt       as GHCExts+#endif+import           GHC.Generics        (Generic)++import           Prelude             hiding (filter, lookup, null, pred)+import qualified Prelude             as P++ infixr 9 .: (.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d (.:) = (.).(.)@@ -88,14 +120,34 @@ {-| A bidirectional map between values of types @a@ and @b@. -}-data Bimap a b = MkBimap !(M.Map a b) !(M.Map b a)+data Bimap a b = MkBimap !(M.Map a b) !(M.Map b a) deriving (Generic)  instance (Show a, Show b) => Show (Bimap a b) where     show x = "fromList " ++ (show . toList $ x)  instance (Eq a, Eq b) => Eq (Bimap a b) where-    (==) bx by = toAscList bx == toAscList by+    (==) = (==) `on` toAscList +instance (Ord a, Ord b) => Ord (Bimap a b) where+    compare = compare `on` toAscList++instance (NFData a, NFData b) => NFData (Bimap a b)++#if __GLASGOW_HASKELL__ >= 708+instance (Ord a, Ord b) => GHCExts.IsList (Bimap a b) where+    type Item (Bimap a b) = (a, b)+    fromList = fromList+    toList = toList+#endif++{-|+A 'Bimap' action failed.+-}+data BimapException = KeyNotFound String+  deriving(Eq, Show, Typeable)++instance Exception BimapException+ {-| /O(1)/. The empty bimap. /Version: 0.2/-} empty :: Bimap a b@@ -174,7 +226,7 @@ tryInsert :: (Ord a, Ord b)           => a -> b -> Bimap a b -> Bimap a b tryInsert x y bi-    | (x `notMember` bi && y `notMemberR` bi) = unsafeInsert x y bi+    | x `notMember` bi && y `notMemberR` bi = unsafeInsert x y bi     | otherwise                               = bi  {-| /O(log n)/.@@ -194,12 +246,12 @@        => Either a b -> Bimap a b -> Bimap a b deleteE e (MkBimap left right) =     MkBimap-        (perhaps M.delete x $ left)-        (perhaps M.delete y $ right)+        (perhaps M.delete x  left)+        (perhaps M.delete y  right)     where     perhaps = maybe id-    x = either Just (flip M.lookup right) e-    y = either (flip M.lookup left) Just  e+    x = either Just (`M.lookup` right) e+    y = either (`M.lookup` left) Just  e  {-| /O(log n)/. Delete a value and its twin from a bimap.@@ -217,28 +269,107 @@ deleteR = deleteE . Right  {-| /O(log n)/.+Update a value at a specific left key with the result of the provided function.++When the left key is not a member of the bimap, the original bimap is returned.-}+adjust :: (Ord a, Ord b) => (b -> b) -> a -> Bimap a b -> Bimap a b+adjust f = adjustWithKey (const f)++{-| /O(log n)/.+Update a value at a specific right key with the result of the provided function.++When the right key is not a member of the bimap, the original bimap is returned.-}+adjustR :: (Ord a, Ord b) => (a -> a) -> b -> Bimap a b -> Bimap a b+adjustR f b = reverseBimap . adjust f b . reverseBimap+  where reverseBimap (MkBimap left right) = MkBimap right left++{-| /O(log n)/.+Adjust a value at a specific left key.++When the left key is not a member of the bimap, the original bimap is returned.-}+adjustWithKey :: (Ord a, Ord b) => (a -> b -> b) -> a -> Bimap a b -> Bimap a b+adjustWithKey f = updateWithKey (\a -> Just . f a)++{-| /O(log n)/.+Adjust a value at a specific right key.++When the right key is not a member of the bimap, the original bimap is returned.-}+adjustWithKeyR :: (Ord a, Ord b) => (b -> a -> a) -> b -> Bimap a b -> Bimap a b+adjustWithKeyR f b = reverseBimap . adjustWithKey f b . reverseBimap+  where reverseBimap (MkBimap left right) = MkBimap right left++{-| /O(log n)/.+The expression (@'update' f a bimap@) updates the right value @b@ at @a@ (if it is in the bimap).++If (@f b@) is 'Nothing', the element is deleted.++If it is (@'Just' y@), the left key @a@ is bound to the new value @y@.-}+update :: (Ord a, Ord b) => (b -> Maybe b) -> a -> Bimap a b -> Bimap a b+update f = updateWithKey (const f)++{-| /O(log n)/.+The expression (@'updateR' f b bimap@) updates the left value @a@ at @b@ (if it is in the bimap).++If (@f a@) is 'Nothing', the element is deleted.++If it is (@'Just' x@), the right key @b@ is bound to the new value @x@.-}+updateR :: (Ord a, Ord b) => (a -> Maybe a) -> b -> Bimap a b -> Bimap a b+updateR f b = reverseBimap . update f b . reverseBimap+  where reverseBimap (MkBimap left right) = MkBimap right left++{-| /O(log n)/.+The expression (@'updateWithKey' f a bimap@) updates the right value @b@ at @a@ (if it is in the bimap).++If (@f a b@) is 'Nothing', the element is deleted.++If it is (@'Just' y@), the left key @a@ is bound to the new value @y@.-}+updateWithKey :: (Ord a, Ord b) => (a -> b -> Maybe b) -> a -> Bimap a b -> Bimap a b+updateWithKey f a (MkBimap left right) = MkBimap left' right' where+  oldB = M.lookup a left+  newB = f a =<< oldB+  oldA = newB >>= (`M.lookup` right) >>= \x -> if x == a then Nothing else Just x+  left' = maybe id M.delete oldA $ M.updateWithKey f a left+  right' = maybe id (`M.insert` a) newB $ maybe id M.delete oldB right++{-| /O(log n)/.+The expression (@'updateWithKeyR' f b bimap@) updates the left value @a@ at @b@ (if it is in the bimap).++If (@f b a@) is 'Nothing', the element is deleted.++If it is (@'Just' x@), the right key @b@ is bound to the new value @x@.-}+updateWithKeyR :: (Ord a, Ord b) => (b -> a -> Maybe a) -> b -> Bimap a b -> Bimap a b+updateWithKeyR f b = reverseBimap . updateWithKey f b . reverseBimap+  where reverseBimap (MkBimap left right) = MkBimap right left+++{-| /O(log n)/. Lookup a left key in the bimap, returning the associated right key.  This function will @return@ the result in the monad, or @fail@ if the value isn't in the bimap. +Note that the signature differs slightly from Data.Map's @lookup@. This one is more general -+it functions the same way as the "original" if @m@ is cast (or inferred) to Maybe.+ /Version: 0.2/-}-lookup :: (Ord a, Ord b, Monad m)-        => a -> Bimap a b -> m b+lookup :: (Ord a, Ord b, MonadThrow m)+       => a -> Bimap a b -> m b lookup x (MkBimap left _) =-    maybe (fail "Data.Bimap.lookup: Left key not found")-          (return)+    maybe (throwM $ KeyNotFound "Data.Bimap.lookup")+          return           (M.lookup x left)  {-| /O(log n)/. A version of 'lookup' that is specialized to the right key, and returns the corresponding left key.++ /Version: 0.2/-}-lookupR :: (Ord a, Ord b, Monad m)+lookupR :: (Ord a, Ord b, MonadThrow m)         => b -> Bimap a b -> m a lookupR y (MkBimap _ right) =-    maybe (fail "Data.Bimap.lookupR: Right key not found")-          (return)+    maybe (throwM $ KeyNotFound "Data.Bimap.lookupR")+          return           (M.lookup y right)  {-| /O(log n)/.@@ -246,26 +377,32 @@ Calls @'error'@ when the key is not in the bimap. /Version: 0.2/-} (!) :: (Ord a, Ord b) => Bimap a b -> a -> b-(!) bi x = case lookup x bi of-    Just y  -> y-    Nothing -> error "Data.Bimap.(!): Left key not found"+(!) bi x = fromMaybe (error "Data.Bimap.(!): Left key not found") $ lookup x bi  {-| /O(log n)/. A version of @(!)@ that is specialized to the right key, and returns the corresponding left key. /Version: 0.2/-} (!>) :: (Ord a, Ord b) => Bimap a b -> b -> a-(!>) bi y = case lookupR y bi of-    Just x  -> x-    Nothing -> error "Data.Bimap.(!>): Right key not found"+(!>) bi y = fromMaybe (error "Data.Bimap.(!>): Right key not found") $ lookupR y bi +{-| /O(log n)/.+See 'lookup'. -}+(!?) :: (Ord a, Ord b, MonadThrow m) => Bimap a b -> a -> m b+(!?) = flip lookup++{-| /O(log n)/.+See 'lookupR'. -}+(!?>) :: (Ord a, Ord b, MonadThrow m) => Bimap a b -> b -> m a+(!?>) = flip lookupR+ {-| /O(n*log n)/. Build a map from a list of pairs. If there are any overlapping pairs in the list, the later ones will override the earlier ones. /Version: 0.2/-} fromList :: (Ord a, Ord b)          => [(a, b)] -> Bimap a b-fromList xs = foldl' (flip . uncurry $ insert) empty xs+fromList = foldl' (flip . uncurry $ insert) empty  {-| /O(n*log n)/. Build a map from a list of pairs. Unlike 'fromList', earlier pairs@@ -281,7 +418,7 @@ /Version: 0.2.2/-} fromAList :: (Ord a, Ord b)           => [(a, b)] -> Bimap a b-fromAList xs = foldl' (flip . uncurry $ tryInsert) empty xs+fromAList = foldl' (flip . uncurry $ tryInsert) empty  {-| /O(n)/. Convert to a list of associated pairs. /Version: 0.2/-}@@ -324,7 +461,7 @@                          => [(a, b)] -> Bimap a b fromAscPairListUnchecked xs = MkBimap     (M.fromAscList xs)-    (M.fromAscList $ map swap  xs)+    (M.fromAscList $ P.map swap  xs)     where     swap (x, y) = (y, x) @@ -427,7 +564,7 @@     [ M.valid left, M.valid right     , (==)         (sort .                M.toList $ left )-        (sort . map flipPair . M.toList $ right)+        (sort . P.map flipPair . M.toList $ right)     ]     where     flipPair (x, y) = (y, x)@@ -452,6 +589,36 @@ fold :: (a -> b -> c -> c) -> c -> Bimap a b -> c fold f z = foldr (uncurry f) z . assocs +{-| /O(n*log n)/+Map a function over all the left keys in the map.+/Version 0.3/-}+map :: Ord c => (a -> c) -> Bimap a b -> Bimap c b+map f (MkBimap left right) =+    MkBimap (M.mapKeys f left) (M.map f right)++{-| /O(n*log n)/+Map a function over all the right keys in the map.+/Version 0.3/-}+mapR :: Ord c => (b -> c) -> Bimap a b -> Bimap a c+mapR f (MkBimap left right) =+    MkBimap (M.map f left) (M.mapKeys f right)++{-| /O(n)/.+Map a strictly increasing function over all left keys in the map.+/The precondition is not checked./+/Version 0.3/-}+mapMonotonic :: (a -> c) -> Bimap a b -> Bimap c b+mapMonotonic f (MkBimap left right) =+    MkBimap (M.mapKeysMonotonic f left) (M.map f right)++{-| /O(n)/.+Map a strictly increasing function over all right keys in the map.+/The precondition is not checked./+/Version 0.3/-}+mapMonotonicR :: (b -> c) -> Bimap a b -> Bimap a c+mapMonotonicR f (MkBimap left right) =+    MkBimap (M.map f left) (M.mapKeysMonotonic f right)+ {-| /O(log n)/. Delete and find the element with maximal left key. Calls @'error'@ if the bimap is empty.@@ -475,7 +642,7 @@ /Version: 0.2.2/-} deleteMax :: (Ord b) => Bimap a b -> Bimap a b deleteMax = snd . deleteFindMax- + {-| /O(log n)/. Delete the element with maximal right key. Calls @'error'@ if the bimap is empty.@@ -491,7 +658,7 @@ findMax = M.findMax . toMap  {-| /O(log n)/.-Find the element with maximal right key. The +Find the element with maximal right key. The right-hand key is the first entry in the pair. Calls @'error'@ if the bimap is empty. /Version: 0.2.2/-}@@ -521,7 +688,7 @@ /Version: 0.2.2/-} deleteMin :: (Ord b) => Bimap a b -> Bimap a b deleteMin = snd . deleteFindMin- + {-| /O(log n)/. Delete the element with minimal right key. Calls @'error'@ if the bimap is empty.@@ -537,10 +704,9 @@ findMin = M.findMin . toMap  {-| /O(log n)/.-Find the element with minimal right key. The +Find the element with minimal right key. The right-hand key is the first entry in the pair. Calls @'error'@ if the bimap is empty. /Version: 0.2.2/-} findMinR :: Bimap a b -> (b, a) findMinR = M.findMin . toMapR-
+ Data/BimapExt.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE Trustworthy #-}+{-|+An auxiliary module that exports the 'IsList' class from "GHC.Exts". We use+this intermediate module to isolate a safe feature from an otherwise non-safe+module, and prevent all of "Data.Bimap" from being marked as not safe just+because we are importing "GHC.Exts".++The module only exports a class, and the class does not define any methods in+an unsafe way. We therefore consider it safe and mark this module as+trustworthy.+-}+module Data.BimapExt (+    IsList(..)+) where++import GHC.Exts (IsList(..))
HISTORY view
@@ -1,3 +1,11 @@+Version 0.3.1 (17 October 2015)++  * Added update and adjust functions (thanks to koral)++Version 0.3.0 (12 Mar 2015)++  * Added map functions+ Version 0.2.4 (25 Aug 2008)    * added filter and partition
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
− Setup.lhs
@@ -1,9 +0,0 @@-#!/usr/bin/env runhaskell-> import Distribution.Simple-> import System.Cmd-> import System.Exit--> main = defaultMainWithHooks (simpleUserHooks { runTests = suite })->     where->     suite _ _ _ _ = system "bash tests.sh" >> return ()-
Test/RunTests.hs view
@@ -8,6 +8,11 @@  import Test.Tests import Test.Util+import System.Exit  main :: IO ()-main = $( extractTests "Test/Tests.hs" )+main = do+  allPassed <- $( extractTests "Test/Tests.hs" )+  if allPassed+    then exitSuccess+    else exitFailure
Test/Tests.hs view
@@ -1,11 +1,11 @@ module Test.Tests where -import Data.List (sort)+import Data.List (nub, sort) import qualified Data.Set as S-import Prelude hiding (null, lookup, filter)+import Prelude hiding (null, lookup, filter,map) import qualified Prelude as P import Test.QuickCheck-import Test.QuickCheck.Batch+import Control.Applicative((<$>))  import Data.Bimap @@ -15,6 +15,9 @@ instance (Ord a, Arbitrary a, Ord b, Arbitrary b)     => Arbitrary (Bimap a b) where     arbitrary = fromList `fmap` arbitrary++instance (Ord a, CoArbitrary a, Ord b, CoArbitrary b)+    => CoArbitrary (Bimap a b) where     coarbitrary = coarbitrary . toList  -- generator for filter/partition classification functions@@ -28,6 +31,8 @@         return $ FilterFunc             ("(\\x y -> x - y < " ++ show pivot ++ ")")             (\x y -> fromIntegral x - fromIntegral y < pivot)+instance (Integral a, CoArbitrary a, Integral b, CoArbitrary b)+    => CoArbitrary (FilterFunc a b) where     coarbitrary _ gen = do         x <- arbitrary         coarbitrary (x :: Int) gen@@ -56,8 +61,8 @@     bi = fromList xs     isMember x = x `pairMember` bi     notUnique (x, y) = -        ((>1) . length . P.filter (== x) . map fst $ xs) ||-        ((>1) . length . P.filter (== y) . map snd $ xs)+        ((>1) . length . P.filter (== x) . P.map fst $ xs) ||+        ((>1) . length . P.filter (== y) . P.map snd $ xs)  -- a bimap created from a list is no larger than the list prop_fromList_size xs = (size $ fromList xs) <= length xs@@ -66,24 +71,21 @@  -- a monotone bimap can be reconstituted via fromAscPairList prop_fromAscPairList_reconstitute xs = and-    [ (not . isBottom) bi'-    , valid bi'+    [ valid bi'     , (bi == bi')     ]     where-    xs' = zip (sort $ map fst xs) (sort $ map snd xs)+    xs' = zip (sort $ P.map fst xs) (sort $ P.map snd xs)     bi :: Bimap Int Integer     bi = fromList xs'     bi' = fromAscPairList . toAscList $ bi  -- fromAscPairList will never produce an invalid bimap-prop_fromAscPairList_check xs = or-    [ isBottom bi-    , valid bi-    ]+prop_fromAscPairList_check xs = valid bi     where+    xs' = zip (nub $ sort $ P.map fst xs) (nub $ sort $ P.map snd xs)     bi :: Bimap Int Integer-    bi = fromAscPairList xs+    bi = fromAscPairList xs'  -- if a pair is a member of the bimap, then both elements are present -- and associated with each other@@ -161,6 +163,21 @@     where     _ = bi :: Bimap Int Integer +-- adjust and fmap are similar+prop_adjust_fmap bi a = l === r+  where+  l = lookup a $ adjust f a bi :: Maybe Integer+  r = f <$> lookup a bi+  _ = bi :: Bimap Int Integer+  f = (1-)++prop_adjustR_fmap bi b = l == r+  where+  l = lookupR b $ adjustR f b bi :: Maybe Int+  r = f <$> lookupR b bi+  _ = bi :: Bimap Int Integer+  f = (3*)+ -- a singleton bimap is valid, has one association, and the two -- given values map to each other prop_singleton x y = let bi = singleton x y in and@@ -442,3 +459,50 @@     where     _ = bi :: Bimap Int Integer +prop_map_preserve_keys bi =+    (Data.List.sort $ P.map f $ keys bi) == (keys $ map f bi)+    where+    f = (4/) -- This is an arbitrary function+    _ = bi :: Bimap Double Integer++prop_map_preserve_lookup bi v =+    (lookup (f v) $ map f bi) == (lookup v bi :: Maybe Integer)+    where+    f = (1-)+    _ = bi :: Bimap Int Integer++prop_map_preserve_right_keys bi =+    (Data.List.sort $ P.map f $ keysR bi) == (keysR $ mapR f bi)+    where+    f = (4/) -- This is an arbitrary function+    _ = bi :: Bimap Int Double++prop_map_preserve_lookupR bi v =+    (lookup v $ mapR f bi) == (f <$> lookup v bi :: Maybe Integer)+    where+    f = (1-)+    _ = bi :: Bimap Int Integer++prop_mapMonotonic_preserve_keys bi =+    (P.map f $ keys bi) == (keys $ mapMonotonic f bi)+    where+    f = (3+) -- This is an arbitrary monotonic function+    _ = bi :: Bimap Double Integer++prop_mapMonotonic_preserve_lookup bi v =+    (lookup (f v) $ mapMonotonic f bi) == (lookup v bi :: Maybe Integer)+    where+    f = (2*)+    _ = bi :: Bimap Int Integer++prop_mapMontonic_preserve_right_keys bi =+    (P.map f $ keysR bi) == (keysR $ mapMonotonicR f bi)+    where+    f = (^2) -- This is an arbitrary monotonic function+    _ = bi :: Bimap Int Double++prop_mapMonotonic_preserve_lookupR bi v =+    (lookup v $ mapMonotonicR f bi) == (f <$> lookup v bi :: Maybe Integer)+    where+    f = (1-)+    _ = bi :: Bimap Int Integer
Test/Util.hs view
@@ -30,19 +30,24 @@     firstToken = fst . head . lex     isProperty = isPrefixOf "prop_" -{- Inspired by & borrowed from: -}--- http://blog.codersbase.com/2006/09/01/simple-unit-testing-in-haskell/-mkCheck name =-    [| printf "%-25s : " name >> quickCheck $(varE (mkName name)) |]+resultIsSuccess Success {} = True+resultIsSuccess _ = False -mkChecks []        = undefined -- if we don't have any tests, then the test suite is undefined right?-mkChecks [name]    = mkCheck name-mkChecks (name:ns) = [| $(mkCheck name) >> $(mkChecks ns) |]+mkCheck' name = [| printf "%-25s : " name+                   >> quickCheckResult $(varE (mkName name))+                   >>= return . resultIsSuccess |]+mkChecks' [] = undefined+mkChecks' [name] = mkCheck' name+mkChecks' (name:ns) = [| do+                          this <- $(mkCheck' name)+                          rest <- $(mkChecks' ns)+                          return $ this && rest |] + {- Extract the names of QuickCheck tests from a file, and splice in a sequence of calls to them. The module doing the splicing must also import the file being processed. -} extractTests :: FilePath -> Q Exp-extractTests = (mkChecks =<<) . runIO . fileProperties+extractTests = (mkChecks' =<<) . runIO . fileProperties
bimap.cabal view
@@ -1,6 +1,6 @@-cabal-version:       >= 1.2.3.0+cabal-version:       >= 1.10 name:                bimap-version:             0.2.4+version:             0.5.0 synopsis:            Bidirectional mapping between two key types description:   A data structure representing a bidirectional mapping between two@@ -9,29 +9,50 @@ category:            Data license:             BSD3 license-file:        LICENSE-copyright:           Stuart Cook and contributors 2008-author:              Stuart Cook and contributors 2008-maintainer:          scook0@gmail.com-homepage:            http://code.haskell.org/bimap--- This build-type is a lie, but we're only using hooks to specify--- a test command, so there shouldn't be any problems.+copyright:           Stuart Cook and contributors 2008, Joel Williamson 2015+author:              Stuart Cook and contributors 2008, Joel Williamson 2015+maintainer:          Joel Williamson <joel@joelwilliamson.ca>+homepage:            https://github.com/joelwilliamson/bimap build-type:          Simple-tested-with:         GHC ==6.6, GHC ==6.8.1+tested-with:         GHC <= 8.6.4 && >= 7.0 extra-source-files:     HISTORY-    tests.sh-    Test/Tests.hs-    Test/Util.hs-    Test/RunTests.hs -flag small-base- Library-  if flag(small-base)-    build-depends:       base >= 3, containers+  build-depends:       base >= 4 && <5, containers, deepseq, exceptions+  if impl(ghc < 7.6.1)+    build-depends: ghc-prim+    default-extensions:  CPP+                         DeriveGeneric+                         TypeFamilies   else-    build-depends:       base < 3+    default-extensions: CPP+                        TypeFamilies+  default-language:    Haskell98      ghc-options:         -Wall   exposed-modules:       Data.Bimap +  if impl(ghc >= 7.8)+    other-modules:+      Data.BimapExt++test-suite tests+    type:            exitcode-stdio-1.0+    main-is:         Test/RunTests.hs+    other-modules:   Test.Tests+                     Test.Util+    build-depends:   base >= 4 && < 5,+                     containers,+                     deepseq,+                     exceptions,+                     QuickCheck >= 2 && < 3,+                     template-haskell >= 2 && < 3+    if impl(ghc < 7.6.1)+      build-depends: ghc-prim+  default-extensions:  TemplateHaskell+  default-language:    Haskell98   ++source-repository head+    type:         git+    location:     https://github.com/joelwilliamson/bimap.git
− tests.sh
@@ -1,8 +0,0 @@-#!/bin/bash--rm .test.log 2>/dev/null-runhaskell Test/RunTests.hs > .test.log || exit 1-cat .test.log || exit 2-grep Falsifiable .test.log >/dev/null && exit 3-echo "~~ all tests passed ~~"-exit 0