[#53] Add doctest integration

This commit is contained in:
Dmitry Kovanikov 2018-01-17 22:25:05 +03:00
parent f0f0621abb
commit 3a6b66b22d
No known key found for this signature in database
GPG Key ID: 9824BEAFD9AF6A3E
17 changed files with 154 additions and 46 deletions

View File

@ -1,3 +1,9 @@
1.0.4
=====
* [#53](https://github.com/serokell/universum/issues/53):
Add `doctest` to `universum`. Also imporove and fix documentation.
1.0.3
=====

View File

@ -12,6 +12,9 @@ module Universum.Applicative
import Control.Applicative (Alternative (..), Applicative (..), Const (..), ZipList (..), liftA2,
liftA3, optional, (<**>))
-- $setup
-- >>> import Universum.Monad (Maybe)
-- | Shorter alias for @pure ()@.
--
-- >>> pass :: Maybe ()

View File

@ -98,6 +98,9 @@ import GHC.Stack (CallStack, HasCallStack, callStack, currentCallStack, getCallS
prettyCallStack, prettySrcLoc, withFrozenCallStack)
#endif
-- $setup
-- >>> import Universum.Function (const, ($))
-- Pending GHC 8.2 we'll expose these.
{-
@ -117,11 +120,13 @@ import Data.Kind as X (
-- | Stricter version of 'Data.Function.$' operator.
-- Default Prelude defines this at the toplevel module, so we do as well.
--
-- >>> const 3 $ undefined
-- >>> const 3 $ Prelude.undefined
-- 3
-- >>> const 3 $! undefined
-- >>> const 3 $! Prelude.undefined
-- *** Exception: Prelude.undefined
-- CallStack (from HasCallStack):
-- error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
-- ...
($!) :: (a -> b) -> a -> b
f $! x = let !vx = x in f vx
infixr 0 $!

View File

@ -9,10 +9,17 @@ import Universum.Bool.Reexport (Bool, guard, unless, when)
import Universum.Function (flip)
import Universum.Monad (Monad, MonadPlus, (>>=))
-- $setup
-- >>> import Universum.Applicative (pure)
-- >>> import Universum.Bool.Reexport (Bool (..))
-- >>> import Universum.Function (($))
-- >>> import Universum.Monad (Maybe (..))
-- >>> import Universum.Print (putTextLn)
-- | Monadic version of 'when'.
--
-- >>> whenM (pure False) $ putText "No text :("
-- >>> whenM (pure True) $ putText "Yes text :)"
-- >>> whenM (pure False) $ putTextLn "No text :("
-- >>> whenM (pure True) $ putTextLn "Yes text :)"
-- Yes text :)
-- >>> whenM (Just True) (pure ())
-- Just ()
@ -26,16 +33,16 @@ whenM p m = p >>= flip when m
-- | Monadic version of 'unless'.
--
-- >>> unlessM (pure False) $ putText "No text :("
-- >>> unlessM (pure False) $ putTextLn "No text :("
-- No text :(
-- >>> unlessM (pure True) $ putText "Yes text :)"
-- >>> unlessM (pure True) $ putTextLn "Yes text :)"
unlessM :: Monad m => m Bool -> m () -> m ()
unlessM p m = p >>= flip unless m
{-# INLINE unlessM #-}
-- | Monadic version of @if-then-else@.
--
-- >>> ifM (pure True) (putText "True text") (putText "False text")
-- >>> ifM (pure True) (putTextLn "True text") (putTextLn "False text")
-- True text
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM p x y = p >>= \b -> if b then x else y
@ -43,14 +50,15 @@ ifM p x y = p >>= \b -> if b then x else y
-- | Monadic version of 'guard'. Occasionally useful.
-- Here some complex but real-life example:
-- @
-- findSomePath :: IO (Maybe FilePath)
--
-- somePath :: MaybeT IO FilePath
-- somePath = do
-- path <- MaybeT findSomePath
-- guardM $ liftIO $ doesDirectoryExist path
-- return path
-- @
-- findSomePath :: IO (Maybe FilePath)
--
-- somePath :: MaybeT IO FilePath
-- somePath = do
-- path <- MaybeT findSomePath
-- guardM $ liftIO $ doesDirectoryExist path
-- return path
-- @
guardM :: MonadPlus m => m Bool -> m ()
guardM f = f >>= guard

View File

@ -41,8 +41,8 @@ module Universum.Container.Class
) where
import Data.Coerce (Coercible, coerce)
import Prelude hiding (all, and, any, elem, foldMap, foldl, foldr, mapM_, notElem, or, product,
sequence_, sum)
import Prelude hiding (all, and, any, elem, foldMap, foldl, foldr, mapM_, notElem, null, or,
product, sequence_, sum)
import Universum.Applicative (Alternative (..), Const, ZipList, pass)
import Universum.Base (Constraint, Word8)
@ -86,6 +86,10 @@ import qualified Data.Vector.Primitive as VP
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Unboxed as VU
-- $setup
-- >>> import Universum.String (Text)
-- >>> import qualified Data.HashMap.Strict as HashMap
----------------------------------------------------------------------------
-- Containers (e.g. tuples aren't containers)
----------------------------------------------------------------------------
@ -228,9 +232,8 @@ class ToPairs t where
type Val t :: *
-- | Converts the structure to the list of the key-value pairs.
-- >>> import qualified Data.HashMap as HashMap
-- >>> toPairs (HashMap.fromList [('a', "xxx"), ('b', "yyy")])
-- [('a', "xxx"), ('b', "yyy")]
-- [('a',"xxx"),('b',"yyy")]
toPairs :: t -> [(Key t, Val t)]
-- | Converts the structure to the list of the keys.
@ -244,7 +247,7 @@ class ToPairs t where
-- | Converts the structure to the list of the values.
--
-- >>> elems (HashMap.fromList [('a', "xxx"), ('b', "yyy")])
-- ["xxx", "yyy"]
-- ["xxx","yyy"]
elems :: t -> [Val t]
elems = map snd . toPairs
{-# INLINE elems #-}
@ -591,27 +594,53 @@ instance Container (Vector a)
-- Derivative functions
----------------------------------------------------------------------------
-- TODO: I should put different strings for different versions but I'm too lazy to do it...
#if MIN_VERSION_base(4,10,1)
-- | Stricter version of 'Prelude.sum'.
--
-- >>> sum [1..10]
-- 55
-- >>> sum (Just 3)
-- <interactive>:43:1: error:
-- ...
-- • Do not use 'Foldable' methods on Maybe
-- • In the expression: sum (Just 3)
-- In an equation for it: it = sum (Just 3)
-- Suggestions:
-- Instead of
-- for_ :: (Foldable t, Applicative f) => t a -> (a -> f b) -> f ()
-- use
-- whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()
-- whenRight :: Applicative f => Either l r -> (r -> f ()) -> f ()
-- ...
-- Instead of
-- fold :: (Foldable t, Monoid m) => t m -> m
-- use
-- maybeToMonoid :: Monoid m => Maybe m -> m
-- ...
#endif
sum :: (Container t, Num (Element t)) => t -> Element t
sum = foldl' (+) 0
#if MIN_VERSION_base(4,10,1)
-- | Stricter version of 'Prelude.product'.
--
-- >>> product [1..10]
-- 3628800
-- >>> product (Right 3)
-- <interactive>:45:1: error:
-- ...
-- • Do not use 'Foldable' methods on Either
-- • In the expression: product (Right 3)
-- In an equation for it: it = product (Right 3)
-- Suggestions:
-- Instead of
-- for_ :: (Foldable t, Applicative f) => t a -> (a -> f b) -> f ()
-- use
-- whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()
-- whenRight :: Applicative f => Either l r -> (r -> f ()) -> f ()
-- ...
-- Instead of
-- fold :: (Foldable t, Monoid m) => t m -> m
-- use
-- maybeToMonoid :: Monoid m => Maybe m -> m
-- ...
#endif
product :: (Container t, Num (Element t)) => t -> Element t
product = foldl' (*) 1

View File

@ -12,6 +12,10 @@ import Universum.Functor.Reexport (Functor (..))
map :: Functor f => (a -> b) -> f a -> f b
map = fmap
-- $setup
-- >>> import Universum.Base (negate)
-- >>> import Universum.Monad (Maybe (..))
-- | Alias for @fmap . fmap@. Convenient to work with two nested 'Functor's.
--
-- >>> negate <<$>> Just [1,2,3]

View File

@ -19,6 +19,13 @@ import Universum.Monoid (NonEmpty (..))
import Universum.Functor (fmap)
import Universum.Monad (Maybe (..))
-- $setup
-- >>> import Universum.Applicative (pure)
-- >>> import Universum.Base ((==), even)
-- >>> import Universum.Bool (Bool (..))
-- >>> import Universum.Container (length)
-- >>> import Universum.Function (($))
-- | Returns default list if given list is empty.
-- Otherwise applies given function to every element.
--

View File

@ -20,19 +20,26 @@ import Universum.Container (Container, Element, fold, toList)
import Universum.Functor (fmap)
import Universum.Monad.Reexport (Monad (..))
-- $setup
-- :set -XOverloadedStrings
-- >>> import Universum.Base (even)
-- >>> import Universum.Monad (Maybe (..), (>=>))
-- >>> import Universum.Print (putTextLn)
-- >>> import Universum.String (readMaybe)
-- | Lifting bind into a monad. Generalized version of @concatMap@
-- that works with a monadic predicate. Old and simpler specialized to list
-- version had next type:
--
-- @
-- concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]
-- concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]
-- @
--
-- Side note: previously it had type
--
-- @
-- concatMapM :: (Applicative q, Monad m, Traversable m)
-- => (a -> q (m b)) -> m a -> q (m b)
-- concatMapM :: (Applicative q, Monad m, Traversable m)
-- => (a -> q (m b)) -> m a -> q (m b)
-- @
--
-- Such signature didn't allow to use this function when traversed container
@ -40,7 +47,7 @@ import Universum.Monad.Reexport (Monad (..))
-- Now you can use it like e.g.
--
-- @
-- concatMapM readFile files >>= putStrLn
-- concatMapM readFile files >>= putTextLn
-- @
concatMapM
:: ( Applicative f
@ -76,7 +83,7 @@ concatForM = flip concatMapM
-- Just False
-- >>> andM [Just True, Nothing]
-- Nothing
-- >>> andM [putStrLn "1" >> pure True, putStrLn "2" >> pure False, putStrLn "3" >> undefined]
-- >>> andM [putTextLn "1" >> pure True, putTextLn "2" >> pure False, putTextLn "3" >> pure True]
-- 1
-- 2
-- False

View File

@ -23,6 +23,8 @@ import Data.Maybe (Maybe (..), maybe)
import Universum.Applicative (pass)
import Universum.Monad.Reexport (Either (..), either)
-- $setup
-- >>> import Universum.Bool (Bool (..))
-- | Extracts value from 'Left' or return given default value.
--
@ -57,7 +59,7 @@ leftToMaybe = either Just (const Nothing)
--
-- >>> rightToMaybe (Left True)
-- Nothing
-- >>> leftToMaybe (Right "aba")
-- >>> rightToMaybe (Right "aba")
-- Just "aba"
rightToMaybe :: Either l r -> Maybe r
rightToMaybe = either (const Nothing) Just
@ -75,7 +77,7 @@ maybeToRight l = maybe (Left l) Right
--
-- >>> maybeToLeft True (Just "aba")
-- Left "aba"
-- >>> maybeToRight True Nothing
-- >>> maybeToLeft True Nothing
-- Right True
maybeToLeft :: r -> Maybe l -> Either l r
maybeToLeft r = maybe (Right r) Left

View File

@ -14,6 +14,11 @@ module Universum.Monad.Maybe
import Universum.Applicative (Applicative, pass, pure)
import Universum.Monad.Reexport (Maybe (..), Monad (..))
-- $setup
-- >>> import Universum.Bool (Bool (..))
-- >>> import Universum.Function (($))
-- >>> import Universum.Print (putTextLn)
-- | Specialized version of 'for_' for 'Maybe'. It's used for code readability.
-- Also helps to avoid space leaks:
-- <http://www.snoyman.com/blog/2017/01/foldable-mapm-maybe-and-recursive-functions Foldable.mapM_ space leak>.
@ -42,9 +47,9 @@ whenNothing Nothing m = m
-- | Performs default 'Applicative' action if 'Nothing' is given.
-- Do nothing for 'Just'. Convenient for discarding 'Just' content.
--
-- >>> whenNothing_ Nothing $ putText "Nothing!"
-- >>> whenNothing_ Nothing $ putTextLn "Nothing!"
-- Nothing!
-- >>> whenNothing_ (Just True) $ putText "Nothing!"
-- >>> whenNothing_ (Just True) $ putTextLn "Nothing!"
whenNothing_ :: Applicative f => Maybe a -> f () -> f ()
whenNothing_ Nothing m = m
whenNothing_ _ _ = pass

View File

@ -23,6 +23,10 @@ import Data.Semigroup (Option (..), Semigroup (sconcat, stimes, (<>)), WrappedMo
import Universum.Monad.Reexport (Maybe, fromMaybe)
-- $setup
-- >>> import Universum.Base (Int)
-- >>> import Universum.Monad (Maybe (..))
-- | Extracts 'Monoid' value from 'Maybe' returning 'mempty' if 'Nothing'.
--
-- >>> maybeToMonoid (Just [1,2,3] :: Maybe [Int])

View File

@ -39,7 +39,7 @@ import qualified Data.Set as Set
-- | Like 'Prelude.nub' but runs in @O(n * log n)@ time and requires 'Ord'.
--
-- >>> ordNub [3, 3, 3, 2, 2, -1, 1]
-- [3, 2, -1, 1]
-- [3,2,-1,1]
ordNub :: (Ord a) => [a] -> [a]
ordNub = go Set.empty
where
@ -52,7 +52,7 @@ ordNub = go Set.empty
-- | Like 'Prelude.nub' but runs in @O(n * log_16(n))@ time and requires 'Hashable'.
--
-- >>> hashNub [3, 3, 3, 2, 2, -1, 1]
-- [3, 2, -1, 1]
-- [3,2,-1,1]
hashNub :: (Eq a, Hashable a) => [a] -> [a]
hashNub = go HashSet.empty
where
@ -65,13 +65,13 @@ hashNub = go HashSet.empty
-- | Like 'ordNub' but also sorts a list.
--
-- >>> sortNub [3, 3, 3, 2, 2, -1, 1]
-- [-1, 1, 2, 3]
-- [-1,1,2,3]
sortNub :: (Ord a) => [a] -> [a]
sortNub = Set.toList . Set.fromList
-- | Like 'hashNub' but has better performance and also doesn't save the order.
--
-- >>> unstableNub [3, 3, 3, 2, 2, -1, 1]
-- [1, 2, 3, -1]
-- [1,2,3,-1]
unstableNub :: (Eq a, Hashable a) => [a] -> [a]
unstableNub = HashSet.toList . HashSet.fromList

View File

@ -44,6 +44,12 @@ import qualified Text.Read (readEither)
import qualified GHC.Show as Show (Show (show))
-- $setup
-- >>> :set -XTypeApplications -XOverloadedStrings
-- >>> import Universum.Base (Int)
-- >>> import Universum.Function (($))
-- >>> import Universum.Print (putStrLn)
-- | Type synonym for 'Data.Text.Lazy.Text'.
type LText = LT.Text

View File

@ -11,6 +11,12 @@ module Universum.VarArg
( SuperComposition(..)
) where
-- $setup
-- >>> import Universum.Base ((+))
-- >>> import Universum.Container (null)
-- >>> import Prelude (show)
-- >>> import Data.List (zip5)
class SuperComposition a b c | a b -> c where
-- | Allows to apply function to result of another function with multiple
-- arguments.

View File

@ -1,9 +1,5 @@
resolver: lts-10.0
resolver: lts-10.3
packages: [.]
nix:
packages: [binutils, gmp]
extra-deps: []
flags: {}
extra-package-dbs: []

9
test/Doctest.hs Normal file
View File

@ -0,0 +1,9 @@
module Main (main) where
import System.FilePath.Glob (glob)
import Test.DocTest (doctest)
main :: IO ()
main = do
sourceFiles <- glob "src/**/*.hs"
doctest $ "-XNoImplicitPrelude" : sourceFiles

View File

@ -1,13 +1,13 @@
name: universum
version: 1.0.3
version: 1.0.4
synopsis: Custom prelude used in Serokell
description: Custom prelude used in Serokell
description: See README.md file for more details.
homepage: https://github.com/serokell/universum
license: MIT
license-file: LICENSE
author: Stephen Diehl, @serokell
maintainer: Serokell <hi@serokell.io>
copyright: 2016 Stephen Diehl, 2016-2017 Serokell
copyright: 2016 Stephen Diehl, 2016-2018 Serokell
category: Prelude
stability: stable
build-type: Simple
@ -92,6 +92,17 @@ library
default-extensions: NoImplicitPrelude
OverloadedStrings
test-suite universum-doctest
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Doctest.hs
build-depends: base >= 4.8 && < 5
, doctest
, Glob
ghc-options: -threaded
default-language: Haskell2010
benchmark universum-benchmark
type: exitcode-stdio-1.0
default-language: Haskell2010