universum 1.0.4.1 → 1.1.0
raw patch · 12 files changed
+423/−231 lines, 12 filesdep +hedgehogdep +tastydep +tasty-hedgehogdep ~Globdep ~basedep ~bytestring
Dependencies added: hedgehog, tasty, tasty-hedgehog
Dependency ranges changed: Glob, base, bytestring, containers, criterion, deepseq, doctest, microlens, microlens-mtl
Files
- CHANGES.md +22/−0
- README.md +21/−10
- benchmark/Main.hs +7/−8
- src/Universum/Base.hs +11/−18
- src/Universum/Container/Class.hs +104/−178
- src/Universum/Exception.hs +34/−2
- src/Universum/Function.hs +3/−2
- src/Universum/Monad/Maybe.hs +18/−1
- src/Universum/Unsafe.hs +35/−0
- test/Spec.hs +9/−0
- test/Test/Universum/Property.hs +129/−0
- universum.cabal +30/−12
CHANGES.md view
@@ -1,3 +1,23 @@+1.1.0+=====++* [#144](https://github.com/serokell/universum/issues/144):+ Add `Exc` pattern synonym.+* [#60](https://github.com/serokell/universum/issues/60):+ Reexport `Natural` type from `Numeric.Natura` module.+* [#118](https://github.com/serokell/universum/issues/118):+ Reexport `Type` from `Data.Kind` module.+* [#130](https://github.com/serokell/universum/issues/130):+ Merge `ToList` and `Container` type classes into single type class `Container`.+* [#15](https://github.com/serokell/universum/issues/15):+ Add `?:` function to `Universum.Monad.Maybe`.+* [#128](https://github.com/serokell/universum/issues/128):+ Add `Unsafe` module with unsafe functions to works with lists and `Maybe`.+* [#129](https://github.com/serokell/universum/issues/129):+ Reexport `id`.+* [#136](https://github.com/serokell/universum/issues/136):+ Change `foldl'` type back, add `flipfoldl'` instead.+ 1.0.4.1 ===== @@ -63,6 +83,8 @@ Remove `NontrivialContainer` constraint alias. * [#56](https://github.com/serokell/universum/issues/56): Make `elem` and `notElem` faster for `Set` and `HashSet` by introducing `ElementConstraint` associated type family.+* Remove `Unsafe` module. Though, see issue [#128](https://github.com/serokell/universum/issues/128)+ for disuccion regarding possible return of this module. 0.9.1 =====
README.md view
@@ -59,6 +59,8 @@ 1. Avoid all [partial functions](https://www.reddit.com/r/haskell/comments/5n51u3/why_are_partial_functions_as_in_head_tail_bad/). We like [total](http://mathworld.wolfram.com/TotalFunction.html) and exception-free functions.+ Though you can still use some unsafe functions from `Universum.Unsafe` module+ but they are not exported by default. 2. Use more efficient [string representations](https://www.reddit.com/r/haskell/comments/29jw0s/whats_wrong_with_string/). `String` type is crushingly inefficient. All our functions either try to be polymorphic over string types, or use [`Text`](http://hackage.haskell.org/package/text-1.2.2.1/docs/Data-Text.html)@@ -116,17 +118,18 @@ Gotchas ------- -* `id` is renamed to `identity` (because it's nice to be able to use `id` as a variable name). * `head`, `tail`, `last`, `init` work with `NonEmpty a` instead of `[a]`. * Safe analogue for `head` function: `safeHead :: [a] -> Maybe a`.-* `undefined` triggers a compiler warning, which is probably not what you want. Either use `throwIO`, `Except`, or `error`.+* `undefined` triggers a compiler warning, which is probably not what you want. Either use `throwIO`, `Except`, `error` or `bug`. * `map` is `fmap` now.-* `sortOn` is available without import. This function efficiently sorts a list based on some- property of its elements (e.g. `sortOn length` would sort elements by length).+* Multiple sorting functions are available without imports:+ + `sortBy :: (a -> a -> Ordering) -> [a] -> [a]`: sorts list using given custom comparator.+ + `sortWith :: Ord b => (a -> b) -> [a] -> [a]`: sorts a list based on some property of its elements.+ + `sortOn :: Ord b => (a -> b) -> [a] -> [a]`: just like `sortWith`, but more time-efficient if function is calculated slowly (though less space-efficient). So you should write `sortOn length` (would sort elements by length) but `sortWith fst` (would sort list of pairs by first element). * Functions `sum` and `product` are strict now, which makes them more efficient. * If you try to do something like `putStrLn "hi"`, you'll get an error message if `OverloadedStrings` is enabled – it happens because the compiler doesn't know what- type to infer for the string. Use `putText` in this case.+ type to infer for the string. Use `putTextLn` in this case. * Since `show` doesn't come from `Show` anymore, you can't write `Show` instances easily. Either use autoderived instances or [`Buildable`](https://github.com/serokell/universum/blob/f2ccf8afd862e37ccd204c0ef9efde48a05c2d29/src/Universum.hs#L144).@@ -143,7 +146,6 @@ * As a consequence of previous point, some functions like `traverse_`, `forM_`, `sequenceA_`, etc. are generalized over `Container` type classes. * `error` takes `Text`.-* `foldl'` takes a function with its arguments flipped compared with the `base` version. Things that you were already using, but now you don't have to import them explicitly@@ -158,7 +160,7 @@ Then, some commonly used types: `Map/HashMap/IntMap`, `Set/HashSet/IntSet`, `Seq`, `Text` and `ByteString` (as well as synonyms `LText` and `LByteString` for lazy versions). -`liftIO` and `MonadIO` are exported by default. A lot of functions are generalized to `MonadIO`.+`liftIO` and `MonadIO` are exported by default. A lot of `IO` functions are generalized to `MonadIO`. `deepseq` is exported. For instance, if you want to force deep evaluation of some value (in IO), you can write `evaluateNF a`. WHNF evaluation is possible with `evaluateWHNF a`.@@ -176,7 +178,7 @@ We export `Text` and `LText`, and some functions work with `Text` instead of `String` – specifically, IO functions (`readFile`, `putStrLn`, etc) and `show`. In fact, `show` is polymorphic and can produce strict or lazy `Text`, `String`, or `ByteString`.-Also, `toS` can convert any string type to any string type, but you can use `toText`, `toByteString`, `toString` functions to convert to any specific type.+Also, `toText/toLText/toString` can convert `Text|LText|String` types to `Text/LText/String`. If you want to convert to and from `ByteString` use `encodeUtf8/decodeUtf8` functions. ### Debugging and `undefined`s @@ -201,7 +203,7 @@ * `ordNub` and `sortNub` are _O(n log n)_ versions of `nub` (which is quadratic) and `hashNub` and `unstableNub` are almost _O(n)_ versions of `nub`. * `(&)` – reverse application. `x & f & g` instead of `g $ f $ x` is useful sometimes.-* `pretty` and `prettyL` for converting `Buildable` into `Text` (can be used instead of `show`).+* `pretty` and `prettyL` for converting `Buildable` into `Text` (suggested be used instead of `show`). * `whenM`, `unlessM`, `ifM`, `guardM` are available and do what you expect them to do (e.g. `whenM (doesFileExist "foo")`). * Very generalized version of `concatMapM`, too, is available and does what expected.@@ -277,7 +279,16 @@ for creating singleton containers. Even monomorhpic ones like `Text`. * `evaluateWHNF` and `evaluateNF` functions as clearer and lifted aliases for `evaluate` and `evaluate . force`.-* `ToPairs` type class for data types that can be converted to list of pairs.+* `ToPairs` type class for data types that can be converted to list of pairs (like `Map` or `HashMap` or `IntMap`).++Projects that use Universum+---------------------------++- [cardano-report-server](https://github.com/input-output-hk/cardano-report-server)+- [cardano-sl](https://github.com/input-output-hk/cardano-sl)+- [importify](https://github.com/serokell/importify)+- [log-warper](https://github.com/serokell/log-warper)+- [orgstat](https://github.com/volhovm/orgstat) License -------
benchmark/Main.hs view
@@ -10,17 +10,17 @@ import Data.List (group, head, nub, sort, zip5) import Data.Text (Text) +import Universum.Container (flipfoldl', foldl') import Universum.Monad (concatMapM) import Universum.Nub (hashNub, ordNub, sortNub, unstableNub) import Universum.VarArg ((...)) import qualified Data.Foldable as Foldable (elem) import qualified Data.HashSet as HashSet (fromList, insert)-import qualified Data.List as List (foldl') import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Set as Set (fromList) import qualified Data.Text as T-import qualified Universum.Container as Container (elem, foldl')+import qualified Universum.Container as Container (elem) main :: IO () main = defaultMain@@ -173,10 +173,9 @@ -- | Checks that 'foldl'' is implemented efficiently for 'Universum.List' bgroupFold :: Benchmark bgroupFold = do- let testList = [1..100000] :: [Int]- let universumFoldl' = Container.foldl' HashSet.insert mempty- let ghcFoldl' = List.foldl' (\hashSet element -> - HashSet.insert element hashSet) mempty- bgroup "foldl'" [ bench "universum" $ nf universumFoldl' testList- , bench "base" $ nf ghcFoldl' testList+ let testList = [1..100000] :: [Int]+ let flipFoldl' = flipfoldl' HashSet.insert mempty+ let ghcFoldl' = foldl' (\hashSet element -> HashSet.insert element hashSet) mempty+ bgroup "foldl'" [ bench "flipped" $ nf flipFoldl' testList+ , bench "base" $ nf ghcFoldl' testList ]
src/Universum/Base.hs view
@@ -11,6 +11,7 @@ , module Data.Char , module Data.Int , module Data.Word+ , Natural -- * Base type classes , module Data.Eq@@ -22,11 +23,10 @@ , module System.IO -- * Base GHC types-#if ( __GLASGOW_HASKELL__ >= 710 ) , module Data.Proxy , module Data.Typeable , module Data.Void-#endif+ , module GHC.Base , module GHC.Enum , module GHC.Exts@@ -47,6 +47,9 @@ , module GHC.OverloadedLabels , module GHC.ExecutionStack , module GHC.Stack++ -- * Data.Kind+ , Type #endif , ($!)@@ -57,6 +60,7 @@ import Data.Char (chr) import Data.Int (Int, Int16, Int32, Int64, Int8) import Data.Word (Word, Word16, Word32, Word64, Word8, byteSwap16, byteSwap32, byteSwap64)+import Numeric.Natural (Natural) -- IO import System.IO (FilePath, Handle, IOMode (..), stderr, stdin, stdout, withFile)@@ -98,24 +102,13 @@ prettyCallStack, prettySrcLoc, withFrozenCallStack) #endif +#if ( __GLASGOW_HASKELL__ >= 800 )+-- TODO: move Constraint here later+import Data.Kind (Type)+#endif+ -- $setup -- >>> import Universum.Function (const, ($))---- Pending GHC 8.2 we'll expose these.--{--import GHC.Records as X (- HasField(..)- )--<<<<<<< HEAD-=======-import Data.Kind as X (- type (*)- , type Type- )--}- -- | Stricter version of 'Data.Function.$' operator. -- Default Prelude defines this at the toplevel module, so we do as well.
src/Universum/Container/Class.hs view
@@ -21,10 +21,11 @@ module Universum.Container.Class ( -- * Foldable-like classes and methods- ToList (..)- , ToPairs (..)+ ToPairs (..) , Container (..) + , flipfoldl'+ , sum , product @@ -64,8 +65,6 @@ import qualified Data.Foldable as Foldable -import qualified Data.List as List (null)- import qualified Data.Sequence as SEQ import qualified Data.ByteString as BS@@ -91,124 +90,6 @@ -- >>> import qualified Data.HashMap.Strict as HashMap ------------------------------------------------------------------------------- Containers (e.g. tuples aren't containers)--------------------------------------------------------------------------------- | Default implementation of 'Element' associated type family.-type family ElementDefault (t :: *) :: * where- ElementDefault (f a) = a---- | Type class for data types that can be converted to List.--- Contains very small and safe subset of 'Foldable' functions.------ You can define 'Tolist' by just defining 'toList' function.--- But the following law should be met:------ @'null' ≡ 'List.null' . 'toList'@----class ToList t where- -- | Type of element for some container. Implemented as an asscociated type family because- -- some containers are monomorphic over element type (like 'T.Text', 'IntSet', etc.)- -- so we can't implement nice interface using old higher-kinded types- -- approach. Implementing this as an associated type family instead of- -- top-level family gives you more control over element types.- type Element t :: *- type Element t = ElementDefault t-- -- | Convert container to list of elements.- --- -- >>> toList (Just True)- -- [True]- -- >>> toList @Text "aba"- -- "aba"- -- >>> :t toList @Text "aba"- -- toList @Text "aba" :: [Char]- toList :: t -> [Element t]- default toList :: (Foldable f, t ~ f a, Element t ~ a) => t -> [Element t]- toList = Foldable.toList- {-# INLINE toList #-}-- -- | Checks whether container is empty.- --- -- >>> null @Text ""- -- True- -- >>> null @Text "aba"- -- False- null :: t -> Bool- null = List.null . toList- {-# INLINE null #-}--------------------------------------------------------------------------------- Instances for monomorphic containers-------------------------------------------------------------------------------instance ToList T.Text where- type Element T.Text = Char- toList = T.unpack- {-# INLINE toList #-}- null = T.null- {-# INLINE null #-}--instance ToList TL.Text where- type Element TL.Text = Char- toList = TL.unpack- {-# INLINE toList #-}- null = TL.null- {-# INLINE null #-}--instance ToList BS.ByteString where- type Element BS.ByteString = Word8- toList = BS.unpack- {-# INLINE toList #-}- null = BS.null- {-# INLINE null #-}--instance ToList BSL.ByteString where- type Element BSL.ByteString = Word8- toList = BSL.unpack- {-# INLINE toList #-}- null = BSL.null- {-# INLINE null #-}--instance ToList IntSet where- type Element IntSet = Int- toList = IS.toList- {-# INLINE toList #-}- null = IS.null- {-# INLINE null #-}--------------------------------------------------------------------------------- Boilerplate instances (duplicate Foldable)--------------------------------------------------------------------------------- Basic types-instance ToList [a]-instance ToList (Maybe a)-instance ToList (Either a b)-instance ToList (Identity a)-instance ToList (Const a b)--#if __GLASGOW_HASKELL__ >= 800--- Algebraic types-instance ToList (Dual a)-instance ToList (First a)-instance ToList (Last a)-instance ToList (Product a)-instance ToList (Sum a)-instance ToList (NonEmpty a)-instance ToList (ZipList a)-#endif---- Containers-instance ToList (HashMap k v)-instance ToList (HashSet v)-instance ToList (IntMap v)-instance ToList (Map k v)-instance ToList (Set v)-instance ToList (Seq a)-instance ToList (Vector a)------------------------------------------------------------------------------ -- ToPairs ---------------------------------------------------------------------------- @@ -284,19 +165,63 @@ elems = M.elems {-# INLINE elems #-} - ------------------------------------------------------------------------------- Additional operations that don't make much sense for e.g. Maybe+-- Containers (e.g. tuples and Maybe aren't containers) ---------------------------------------------------------------------------- --- | A class for 'ToList's that aren't trivial like 'Maybe' (e.g. can hold--- more than one value)-class ToList t => Container t where+-- | Default implementation of 'Element' associated type family.+type family ElementDefault (t :: *) :: * where+ ElementDefault (f a) = a++-- | Very similar to 'Foldable' but also allows instances for monomorphic types+-- like 'Text' but forbids instances for 'Maybe' and similar. This class is used as+-- a replacement for 'Foldable' type class. It solves the following problems:+--+-- 1. 'length', 'foldr' and other functions work on more types for which it makes sense.+-- 2. You can't accidentally use 'length' on polymorphic 'Foldable' (like list),+-- replace list with 'Maybe' and then debug error for two days.+-- 3. More efficient implementaions of functions for polymorphic types (like 'elem' for 'Set').+--+-- The drawbacks:+--+-- 1. Type signatures of polymorphic functions look more scary.+-- 2. Orphan instances are involved if you want to use 'foldr' (and similar) on types from libraries.+class Container t where+ -- | Type of element for some container. Implemented as an asscociated type family because+ -- some containers are monomorphic over element type (like 'T.Text', 'IntSet', etc.)+ -- so we can't implement nice interface using old higher-kinded types+ -- approach. Implementing this as an associated type family instead of+ -- top-level family gives you more control over element types.+ type Element t :: *+ type Element t = ElementDefault t+ -- | Constraint for elements. This can be used to implement more efficient- -- implementation of some methods.+ -- implementation of some methods. For example 'elem' for 'Set' and 'HashSet'. type ElementConstraint t :: * -> Constraint type ElementConstraint t = Eq + -- | Convert container to list of elements.+ --+ -- >>> toList @Text "aba"+ -- "aba"+ -- >>> :t toList @Text "aba"+ -- toList @Text "aba" :: [Char]+ toList :: t -> [Element t]+ default toList :: (Foldable f, t ~ f a, Element t ~ a) => t -> [Element t]+ toList = Foldable.toList+ {-# INLINE toList #-}++ -- | Checks whether container is empty.+ --+ -- >>> null @Text ""+ -- True+ -- >>> null @Text "aba"+ -- False+ null :: t -> Bool+ default null :: (Foldable f, t ~ f a, Element t ~ a) => t -> Bool+ null = Foldable.null+ {-# INLINE null #-}+ foldr :: (Element t -> b -> b) -> b -> t -> b default foldr :: (Foldable f, t ~ f a, Element t ~ a) => (Element t -> b -> b) -> b -> t -> b foldr = Foldable.foldr@@ -307,9 +232,9 @@ foldl = Foldable.foldl {-# INLINE foldl #-} - foldl' :: (Element t -> b -> b) -> b -> t -> b- default foldl' :: (Foldable f, t ~ f a, Element t ~ a) => (Element t -> b -> b) -> b -> t -> b- foldl' f = Foldable.foldl' (flip f)+ foldl' :: (b -> Element t -> b) -> b -> t -> b+ default foldl' :: (Foldable f, t ~ f a, Element t ~ a) => (b -> Element t -> b) -> b -> t -> b+ foldl' = Foldable.foldl' {-# INLINE foldl' #-} length :: t -> Int@@ -411,11 +336,16 @@ ---------------------------------------------------------------------------- instance Container T.Text where+ type Element T.Text = Char+ toList = T.unpack+ {-# INLINE toList #-}+ null = T.null+ {-# INLINE null #-} foldr = T.foldr {-# INLINE foldr #-} foldl = T.foldl {-# INLINE foldl #-}- foldl' f = T.foldl' (flip f)+ foldl' = T.foldl' {-# INLINE foldl' #-} foldr1 = T.foldr1 {-# INLINE foldr1 #-}@@ -439,11 +369,16 @@ {-# INLINE safeHead #-} instance Container TL.Text where+ type Element TL.Text = Char+ toList = TL.unpack+ {-# INLINE toList #-}+ null = TL.null+ {-# INLINE null #-} foldr = TL.foldr {-# INLINE foldr #-} foldl = TL.foldl {-# INLINE foldl #-}- foldl' f = TL.foldl' (flip f)+ foldl' = TL.foldl' {-# INLINE foldl' #-} foldr1 = TL.foldr1 {-# INLINE foldr1 #-}@@ -468,11 +403,16 @@ {-# INLINE safeHead #-} instance Container BS.ByteString where+ type Element BS.ByteString = Word8+ toList = BS.unpack+ {-# INLINE toList #-}+ null = BS.null+ {-# INLINE null #-} foldr = BS.foldr {-# INLINE foldr #-} foldl = BS.foldl {-# INLINE foldl #-}- foldl' f = BS.foldl' (flip f)+ foldl' = BS.foldl' {-# INLINE foldl' #-} foldr1 = BS.foldr1 {-# INLINE foldr1 #-}@@ -498,11 +438,16 @@ {-# INLINE safeHead #-} instance Container BSL.ByteString where+ type Element BSL.ByteString = Word8+ toList = BSL.unpack+ {-# INLINE toList #-}+ null = BSL.null+ {-# INLINE null #-} foldr = BSL.foldr {-# INLINE foldr #-} foldl = BSL.foldl {-# INLINE foldl #-}- foldl' f = BSL.foldl' (flip f)+ foldl' = BSL.foldl' {-# INLINE foldl' #-} foldr1 = BSL.foldr1 {-# INLINE foldr1 #-}@@ -528,11 +473,16 @@ {-# INLINE safeHead #-} instance Container IntSet where+ type Element IntSet = Int+ toList = IS.toList+ {-# INLINE toList #-}+ null = IS.null+ {-# INLINE null #-} foldr = IS.foldr {-# INLINE foldr #-} foldl = IS.foldl {-# INLINE foldl #-}- foldl' f = IS.foldl' (flip f)+ foldl' = IS.foldl' {-# INLINE foldl' #-} length = IS.size {-# INLINE length #-}@@ -596,6 +546,16 @@ -- TODO: I should put different strings for different versions but I'm too lazy to do it... +{- | Similar to 'foldl'' but takes a function with its arguments flipped.++>>> flipfoldl' (/) 5 [2,3] :: Rational+15 % 2++-}+flipfoldl' :: (Container t, Element t ~ a) => (a -> b -> b) -> b -> t -> b+flipfoldl' f = foldl' (flip f)+{-# INLINE flipfoldl' #-}+ #if MIN_VERSION_base(4,10,1) -- | Stricter version of 'Prelude.sum'. --@@ -710,51 +670,17 @@ :$$: Text "" #endif -#define DISALLOW_TO_LIST_8(t, z) \- instance TypeError (DisallowInstance z) => \- ToList (t) where { \- toList = undefined; \- null = undefined; } \--#define DISALLOW_CONTAINER_8(t, z) \- instance TypeError (DisallowInstance z) => \- Container (t) where { \- foldr = undefined; \- foldl = undefined; \- foldl' = undefined; \- length = undefined; \- elem = undefined; \- maximum = undefined; \- minimum = undefined; } \--#define DISALLOW_TO_LIST_7(t) \- instance ForbiddenFoldable (t) => ToList (t) where { \- toList = undefined; \- null = undefined; } \--#define DISALLOW_CONTAINER_7(t) \- instance ForbiddenFoldable (t) => Container (t) where { \- foldr = undefined; \- foldl = undefined; \- foldl' = undefined; \- length = undefined; \- elem = undefined; \- maximum = undefined; \- minimum = undefined; } \- #if __GLASGOW_HASKELL__ >= 800-DISALLOW_TO_LIST_8((a, b),"tuples")-DISALLOW_CONTAINER_8((a, b),"tuples")-DISALLOW_CONTAINER_8(Maybe a,"Maybe")-DISALLOW_CONTAINER_8(Identity a,"Identity")-DISALLOW_CONTAINER_8(Either a b,"Either")+instance TypeError (DisallowInstance "tuple") => Container (a, b)+instance TypeError (DisallowInstance "Maybe") => Container (Maybe a)+instance TypeError (DisallowInstance "Either") => Container (Either a b)+instance TypeError (DisallowInstance "Identity") => Container (Identity a) #else class ForbiddenFoldable a-DISALLOW_TO_LIST_7((a, b))-DISALLOW_CONTAINER_7((a, b))-DISALLOW_CONTAINER_7(Maybe a)-DISALLOW_CONTAINER_7(Identity a)-DISALLOW_CONTAINER_7(Either a b)+instance ForbiddenFoldable (a, b) => Container (a, b)+instance ForbiddenFoldable (Maybe a) => Container (Maybe a)+instance ForbiddenFoldable (Either a b) => Container (Either a b)+instance ForbiddenFoldable (Identity a) => Container (Identity a) #endif ----------------------------------------------------------------------------
src/Universum/Exception.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE Safe #-}+{-# LANGUAGE ViewPatterns #-} -- | Re-exports most useful functionality from 'safe-exceptions'. Also -- provides some functions to work with exceptions over 'MonadError'.@@ -10,6 +12,7 @@ #if ( __GLASGOW_HASKELL__ >= 800 ) , Bug (..) , bug+ , pattern Exc #endif , note ) where@@ -20,9 +23,9 @@ catchAny, displayException, finally, handleAny, mask_, onException, throwM, try, tryAny) -import Control.Applicative (Applicative (pure)) import Control.Monad.Except (MonadError, throwError)-import Data.Maybe (Maybe, maybe)+import Universum.Applicative (Applicative (pure))+import Universum.Monad (Maybe (..), maybe) #if ( __GLASGOW_HASKELL__ >= 800 ) import Data.List ((++))@@ -55,4 +58,33 @@ #else note :: (MonadError e m, Applicative m) => e -> Maybe a -> m a note err = maybe (throwError err) pure+#endif+++#if ( __GLASGOW_HASKELL__ >= 800 )+{- | Pattern synonym to easy pattern matching on exceptions. So intead of+writing something like this:++@+isNonCriticalExc e+ | Just (_ :: NodeAttackedError) <- fromException e = True+ | Just DialogUnexpected{} <- fromException e = True+ | otherwise = False+@++you can use 'Exc' pattern synonym:++@+isNonCriticalExc = \case+ Exc (_ :: NodeAttackedError) -> True -- matching all exceptions of type 'NodeAttackedError'+ Exc DialogUnexpected{} -> True+ _ -> False+@++This pattern is bidirectional. You can use @Exc e@ instead of @toException e@.+-}+pattern Exc :: Exception e => e -> SomeException+pattern Exc e <- (fromException -> Just e)+ where+ Exc e = toException e #endif
src/Universum/Function.hs view
@@ -3,8 +3,9 @@ , identity ) where -import Data.Function (const, fix, flip, on, ($), (.))+import Data.Function (const, fix, flip, id, on, ($), (.)) -- | Renamed version of 'Prelude.id'. identity :: a -> a-identity x = x+identity = id+{-# INLINE identity #-}
src/Universum/Monad/Maybe.hs view
@@ -3,7 +3,8 @@ -- | Utility functions to work with 'Data.Maybe' data type as monad. module Universum.Monad.Maybe- ( whenJust+ ( (?:)+ , whenJust , whenJustM , whenNothing , whenNothing_@@ -12,12 +13,28 @@ ) where import Universum.Applicative (Applicative, pass, pure)+import Universum.Monad.Reexport (fromMaybe) import Universum.Monad.Reexport (Maybe (..), Monad (..)) -- $setup -- >>> import Universum.Bool (Bool (..)) -- >>> import Universum.Function (($)) -- >>> import Universum.Print (putTextLn)+-- >>> import Universum.String (readMaybe)++{- | Similar to 'fromMaybe' but with flipped arguments.++>>> readMaybe "True" ?: False+True++>>> readMaybe "Tru" ?: False+False++-}+infixr 0 ?:+(?:) :: Maybe a -> a -> a+mA ?: b = fromMaybe b mA+{-# INLINE (?:) #-} -- | Specialized version of 'for_' for 'Maybe'. It's used for code readability. -- Also helps to avoid space leaks:
+ src/Universum/Unsafe.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE Unsafe #-}++{- | Unsafe functions to work with lists and 'Maybe'.+Sometimes unavoidable but better don't use them. This module+is intended to be imported qualified and it's not even included+in default prelude exports.++@+import qualified Universum.Unsafe as Unsafe++foo :: [a] -> a+foo = Unsafe.head+@++-}++module Universum.Unsafe+ ( head+ , tail+ , init+ , last+ , at+ , (!!)+ , fromJust+ ) where++import Data.List (head, init, last, tail, (!!))+import Data.Maybe (fromJust)++import Universum.Base (Int)+import Universum.Function (flip)++-- | Similar to '!!' but with flipped arguments.+at :: Int -> [a] -> a+at = flip (!!)
+ test/Spec.hs view
@@ -0,0 +1,9 @@+module Main where++import Test.Tasty (defaultMain)++import Test.Universum.Property (hedgehogTestTree)++main :: IO ()+main = do+ defaultMain hedgehogTestTree
+ test/Test/Universum/Property.hs view
@@ -0,0 +1,129 @@+module Test.Universum.Property+ ( hedgehogTestTree+ ) where++import Universum ++import Data.List (nub)+import Hedgehog (Property, Gen, MonadGen, forAll, property, assert, (===))+import Test.Tasty (testGroup, TestTree)+import Test.Tasty.Hedgehog++import qualified Universum as U+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.Text as T+import qualified Data.Text.Lazy as LT++++hedgehogTestTree :: TestTree+hedgehogTestTree = testGroup "Tests" [utfProps, listProps, boolMProps]++utfProps :: TestTree+utfProps = testGroup "utf8 conversion property tests" + [ testProperty "String to ByteString invertible" prop_StringToBytes+ , testProperty "Text to ByteString invertible" prop_TextToBytes+ , testProperty "ByteString to Text or String invertible" prop_BytesTo+ ]++unicode' :: MonadGen m => m U.Char+unicode' = do+ a <- Gen.unicode+ if U.elem a ['\65534', '\65535']+ then unicode'+ else return a++utf8String :: Gen U.String+utf8String = Gen.string (Range.linear 0 10000) unicode'++utf8Text :: Gen T.Text+utf8Text = Gen.text (Range.linear 0 10000) unicode'++utf8Bytes :: Gen B.ByteString+utf8Bytes = Gen.utf8 (Range.linear 0 10000) unicode'++-- "\65534" fails, but this is from BU.toString+-- > import qualified Data.ByteString.UTF8 as BU+-- > BU.toString (BU.fromString "\65534") == "\65533"+-- > True ++prop_StringToBytes :: Property+prop_StringToBytes = property $ do+ str <- forAll utf8String+ assert $ str == (decodeUtf8 (encodeUtf8 str :: B.ByteString))+ && str == (decodeUtf8 (encodeUtf8 str :: LB.ByteString))+++prop_TextToBytes :: Property+prop_TextToBytes = property $ do+ txt <- forAll utf8Text+ assert $ txt == (decodeUtf8 (encodeUtf8 txt :: B.ByteString))+ && txt == (decodeUtf8 (encodeUtf8 txt :: LB.ByteString))++-- "\239\191\190" fails, but this is the same as "\65534" :: String+prop_BytesTo :: Property+prop_BytesTo = property $ do+ utf <- forAll utf8Bytes+ assert $ utf == (encodeUtf8 (decodeUtf8 utf :: U.String))+ && utf == (encodeUtf8 (decodeUtf8 utf :: T.Text))+ && utf == (encodeUtf8 (decodeUtf8 utf :: LT.Text))++-- ordNub++listProps :: TestTree+listProps = testGroup "list function property tests" + [ testProperty "Hedgehog ordNub xs == nub xs" prop_ordNubCorrect+ , testProperty "Hedgehog hashNub xs == nub xs" prop_hashNubCorrect+ , testProperty "Hedgehog sortNub xs == sort $ nub xs" prop_sortNubCorrect+ , testProperty "Hedgehog sort $ unstableNub xs == sort $ nub xs" prop_unstableNubCorrect+ ]++genIntList :: Gen [U.Int]+genIntList = Gen.list (Range.linear 0 10000) Gen.enumBounded++prop_ordNubCorrect :: Property+prop_ordNubCorrect = property $ do+ xs <- forAll genIntList+ U.ordNub xs === nub xs++prop_hashNubCorrect :: Property+prop_hashNubCorrect = property $ do+ xs <- forAll genIntList+ U.hashNub xs === nub xs++prop_sortNubCorrect :: Property+prop_sortNubCorrect = property $ do+ xs <- forAll genIntList+ U.sortNub xs === (U.sort $ nub xs)++prop_unstableNubCorrect :: Property+prop_unstableNubCorrect = property $ do+ xs <- forAll genIntList+ (U.sort $ U.unstableNub xs) === (U.sort $ nub xs)+++-- logicM+-- this section needs a little more thought++genBoolList :: Gen [U.Bool]+genBoolList = Gen.list (Range.linear 0 1000) Gen.bool++boolMProps :: TestTree+boolMProps = testGroup "lifted logic function property tests"+ [ testProperty "Hedgehog andM" prop_andM+ , testProperty "Hedgehog orM" prop_orM+ ]++prop_andM :: Property+prop_andM = property $ do+ bs <- forAll genBoolList+ U.andM (return <$> bs) === ((return $ U.and bs) :: U.Maybe U.Bool)++prop_orM :: Property+prop_orM = property $ do+ bs <- forAll genBoolList+ U.orM (return <$> bs) === ((return $ U.or bs) :: U.Maybe U.Bool)+
universum.cabal view
@@ -1,5 +1,5 @@ name: universum-version: 1.0.4.1+version: 1.1.0 cabal-version: >=1.18 build-type: Simple license: MIT@@ -62,16 +62,17 @@ Universum.String.Conversion Universum.String.Reexport Universum.TypeOps+ Universum.Unsafe Universum.VarArg build-depends: base >=4.8 && <5,- bytestring >=0.10.8.2,- containers >=0.5.10.2,- deepseq >=1.4.3.0,+ bytestring >=0.10.8.1,+ containers >=0.5.7.1,+ deepseq >=1.4.2.0, ghc-prim >=0.4.0.0, hashable >=1.2.6.1,- microlens >=0.4.8.3,- microlens-mtl >=0.1.11.1,+ microlens >=0.4.8.1,+ microlens-mtl >=0.1.11.0, mtl >=2.2.1, safe-exceptions >=0.1.6.0, stm >=2.4.4.1,@@ -87,13 +88,30 @@ hs-source-dirs: src ghc-options: -Wall -fwarn-implicit-prelude +test-suite universum-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-depends:+ base >=4.8 && <5,+ universum -any,+ bytestring >=0.10.8.1,+ text >=1.2.2.2,+ utf8-string >=1.0.1.1,+ hedgehog >=0.5.1,+ tasty >=0.11.3,+ tasty-hedgehog >=0.1.0.2+ default-language: Haskell2010+ hs-source-dirs: test+ other-modules:+ Test.Universum.Property+ ghc-options: -Wall -threaded test-suite universum-doctest type: exitcode-stdio-1.0 main-is: Doctest.hs build-depends: base >=4.8 && <5,- doctest >=0.13.0,- Glob >=0.9.1+ doctest >=0.11.4,+ Glob >=0.8.0 default-language: Haskell2010 hs-source-dirs: test ghc-options: -threaded@@ -102,11 +120,11 @@ type: exitcode-stdio-1.0 main-is: Main.hs build-depends:- base >=4.10.1.0 && <5,+ base >=4.9.1.0 && <5, universum -any,- containers >=0.5.10.2,- criterion >=1.2.6.0,- deepseq >=1.4.3.0,+ containers >=0.5.7.1,+ criterion >=1.1.4.0,+ deepseq >=1.4.2.0, hashable >=1.2.6.1, mtl >=2.2.1, semigroups >=0.18.3,