fakedata 1.0.2 → 1.0.3
raw patch · 5 files changed
+326/−153 lines, 5 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Faker: FixedSeed :: NonDeterministicSeed
+ Faker: NewSeed :: NonDeterministicSeed
+ Faker: data NonDeterministicSeed
+ Faker: generateNonDeterministicWithFixedSeed :: MonadIO m => FakeT m a -> m a
+ Faker: getNonDeterministicSeed :: FakerSettings -> NonDeterministicSeed
+ Faker: setNonDeterministicSeed :: NonDeterministicSeed -> FakerSettings -> FakerSettings
- Faker: type Fake a = FakeT IO a
+ Faker: type Fake = FakeT IO
Files
- ChangeLog.md +35/−2
- README.md +142/−74
- fakedata.cabal +2/−2
- src/Faker.hs +143/−75
- test/CombinatorsSpec.hs +4/−0
ChangeLog.md view
@@ -1,10 +1,43 @@ # Changelog for fakedata +## 1.0.3++* [Make the `Fake` type synonym partially applied](https://github.com/fakedata-haskell/fakedata/pull/45)+* Implement deterministic output with fixed seed.++This is the behavior of `generateNonDeterministic` vs `generate`+function:++``` haskell+> generate $ listOf 5 $ fromRange (1,100)+[39,39,39,39,39]+> generate $ listOf 5 $ fromRange (1,100)+[39,39,39,39,39]+> generateNonDeterministic $ listOf 5 $ fromRange (1,100)+[94,18,17,48,17]+> generateNonDeterministic $ listOf 5 $ fromRange (1,100)+[15,2,47,85,94]+```++As you can see with `generate` we get deterministic value output and+they are all same in this case. With `generateNonDeterministic`+function we use a different seed each time which results in generating+different values.++There are times when we instead want to generate non deterministic+values but with a fixed seed. And for such use case, we have+implemented `generateNonDeterministicWithFixedSeed` function:++``` haskell+> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+[98,87,77,33,98]+> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+[98,87,77,33,98]+```+ ## 1.0.2 * Make it compatible with aeson 1 and 2.--### Breaking changes ## 1.0.1
README.md view
@@ -10,47 +10,45 @@ **Table of Contents** - [fakedata](#fakedata)- - [Tutorial](#tutorial)- - [Generating address](#generating-address)- - [Generating name](#generating-name)- - [Generate quotes from the movie Back to the Future](#generate-quotes-from-the-movie-back-to-the-future)- - [Combining Fake datas](#combining-fake-datas)- - [Combinators](#combinators)- - [listOf](#listof)- - [oneOf](#oneof)- - [suchThat](#suchthat)- - [Using the `FakeT` transformer](#using-the-faket-transformer)- - [Comparision with other libraries](#comparision-with-other-libraries)- - [Acknowledgments](#acknowledgments)+- [Tutorial](#tutorial)+ - [Generating address](#generating-address)+ - [Generating name](#generating-name)+ - [Generate quotes from the movie Back to the Future](#generate-quotes-from-the-movie-back-to-the-future)+ - [Combining Fake datas](#combining-fake-datas)+- [Deterministic vs Non Deterministic values](#deterministic-vs-non-deterministic-values)+- [Combinators](#combinators)+ - [listOf](#listof)+ - [oneOf](#oneof)+ - [suchThat](#suchthat)+- [Using the `FakeT` transformer](#using-the-faket-transformer)+- [Comparision with other libraries](#comparision-with-other-libraries)+- [Acknowledgments](#acknowledgments) <!-- markdown-toc end --> -fakedata-======== -This library is a port of Ruby's-[faker](https://github.com/stympy/faker). It's a library for producing-fake data such as names, addressess and phone numbers. Note that it-directly uses the source data from that library, so the quality of-fake data is quite high!+# fakedata -This packages comes in handy when you have to generate large amount of+This library is a port of Ruby's [faker](https://github.com/stympy/faker). It's a library for+producing fake data such as names, addressess and phone numbers. Note+that it directly uses the source data from that library, so the+quality of fake data is quite high!++This package comes in handy when you have to generate large amount of real like data for various purposes. I have personally used it for websites where it needs some realistic data in the initial stage, loading database with real like values etc. There are companies which-have used this [for sophisphicated testing-purpose](https://www.reddit.com/r/haskell/comments/o5n71e/fakedata10_haskell_library_for_producing_quality/h2qxw8a?utm_source=share&utm_medium=web2x&context=3).+have used this [for sophisphicated testing purpose](https://www.reddit.com/r/haskell/comments/o5n71e/fakedata10_haskell_library_for_producing_quality/h2qxw8a?utm_source=share&utm_medium=web2x&context=3). Additionly, there are two other packages for creating generators which-is useful property testing:+is useful for property testing: * [fakedata-quickcheck](https://github.com/fakedata-haskell/fakedata-quickcheck) * [hedgehog-fakedata](https://github.com/parsonsmatt/hedgehog-fakedata) -Tutorial---------+# Tutorial -### Generating address+## Generating address ``` {.shellsession} ~/g/fakedata (master) $ stack ghci@@ -61,7 +59,7 @@ "Apt. 298 340 Ike Mission, Goldnertown, FL 19488-9259" ``` -### Generating name+## Generating name ``` {.shellsession} λ> fullName <- generate name@@ -69,20 +67,22 @@ "Sherryl Steuber" ``` -### Generate quotes from the movie Back to the Future+## Generate quotes from the movie Back to the Future - λ> import Faker.Movie.BackToTheFuture- λ> import Faker.Combinators- λ> qs <- generateNonDeterministic $ listOf 5 quotes- λ> qs- [ "Yes. Yes. I'm George. George McFly. I'm your density. I mean, your destiny."- , "Hello? Hello? Anybody home? Huh? Think, McFly. Think! I gotta have time to get them retyped. Do you realize what would happen if I hand in my reports in your handwriting? I'll get fired. You wouldn't want that to happen, would ya? Would ya?"- , "Lorraine. My density has brought me to you."- , "See you in about 30 years."- , "You really think I ought to swear?"- ]+``` haskell+λ> import Faker.Movie.BackToTheFuture+λ> import Faker.Combinators+λ> qs <- generateNonDeterministic $ listOf 5 quotes+λ> qs+[ "Yes. Yes. I'm George. George McFly. I'm your density. I mean, your destiny."+, "Hello? Hello? Anybody home? Huh? Think, McFly. Think! I gotta have time to get them retyped. Do you realize what would happen if I hand in my reports in your handwriting? I'll get fired. You wouldn't want that to happen, would ya? Would ya?"+, "Lorraine. My density has brought me to you."+, "See you in about 30 years."+, "You really think I ought to swear?"+]+``` -### Combining Fake datas+## Combining Fake datas ``` {.haskell} {-#LANGUAGE RecordWildCards#-}@@ -111,11 +111,13 @@ And on executing them: - $ stack name.hs- Person- { personName = "Sherryl Steuber"- , personAddress = "Apt. 298 340 Ike Mission, Goldnertown, FL 19488-9259"- }+``` haskell+$ stack name.hs+Person+ { personName = "Sherryl Steuber"+ , personAddress = "Apt. 298 340 Ike Mission, Goldnertown, FL 19488-9259"+ }+``` You would have noticed in the above output that the name and address are the same as generated before in the GHCi REPL. That's because, by@@ -123,12 +125,14 @@ different set of output each time, you would have to modify the random generator output: - main :: IO ()- main = do- gen <- newStdGen- let settings = setRandomGen gen defaultFakerSettings- person <- generateWithSettings settings fakePerson- print person+``` haskell+main :: IO ()+main = do+ gen <- newStdGen+ let settings = setRandomGen gen defaultFakerSettings+ person <- generateWithSettings settings fakePerson+ print person+``` And on executing the program, you will get a different output: @@ -158,18 +162,90 @@ print person ``` -### Combinators+# Deterministic vs Non Deterministic values -#### listOf+We have various function for generating fake values: +- generate+- generateNonDeterministic+- generateNonDeterministicWithFixedSeed++By default, `generate` produces deterministic values. It's performance+is better than the others and for cases where we are going to generate+a single fake value using record type, it's a good default to+have. Example:+ ``` {.haskell}+{-#LANGUAGE RecordWildCards#-}++import Faker+import Faker.Name+import Faker.Address+import Data.Text++data Person = Person {+ personName :: Text,+ personAddress :: Text+} deriving (Show, Eq)++fakePerson :: Fake Person+fakePerson = do+ personName <- name+ personAddress <- fullAddress+ pure $ Person{..}++main :: IO ()+main = do+ person <- generate fakePerson+ print person+```++And executing it, you will get:++``` haskell+Person+ { personName = "Sherryl Steuber"+ , personAddress = "Apt. 298 340 Ike Mission, Goldnertown, FL 19488-9259"+ }+```++While, it's a good default we would need non deterministic output for+certain cases:++``` haskell+> generate $ listOf 5 $ fromRange (1,100)+[39,39,39,39,39]+> generate $ listOf 5 $ fromRange (1,100)+[39,39,39,39,39]+> generateNonDeterministic $ listOf 5 $ fromRange (1,100)+[94,18,17,48,17]+> generateNonDeterministic $ listOf 5 $ fromRange (1,100)+[15,2,47,85,94]+```++Not how `generateNonDeterministic` is generating different values each+time. If you instead want to have a fixed seed, you should use+`generateNonDeterministicWithFixedSeed` instead:++``` haskell+> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+[98,87,77,33,98]+> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+[98,87,77,33,98]+```++# Combinators++## listOf++``` {.haskell} λ> import Faker.Address λ> item <- generateNonDeterministic $ listOf 5 country λ> item ["Ecuador","French Guiana","Faroe Islands","Canada","Armenia"] ``` -#### oneOf+## oneOf ``` {.haskell} λ> item <- generate $ oneof [country, fullAddress]@@ -177,7 +253,7 @@ "Suite 599 599 Brakus Flat, South Mason, MT 59962-6876" ``` -#### suchThat+## suchThat ``` {.shellsession} λ> import qualified Faker.Address as AD@@ -193,8 +269,7 @@ `Faker.Combinators`. -Using the `FakeT` transformer------------------------------+# Using the `FakeT` transformer When generating values, you may want to perform some side-effects. @@ -255,33 +330,26 @@ go = replicateM 1000 logQuote ``` --Comparision with other libraries---------------------------------+# Comparision with other libraries There are two other libraries in the Hackage providing fake data: -- [faker](https://hackage.haskell.org/package/faker-0.0.0.2)-- [fake](https://hackage.haskell.org/package/fake-0.1.1.1)+- [faker](https://hackage.haskell.org/package/faker-0.0.0.2)+- [fake](https://hackage.haskell.org/package/fake-0.1.1.1) The problem with both the above libraries is that the library covers only a very small amount of fake data source. I wanted to have an-equivalent functionality with something like-[faker](https://github.com/stympy/faker). Also, most of the-combinators in this packages has been inspired (read as taken) from-the `fake` library. Also, `fakedata` offers fairly good amount of+equivalent functionality with something like [faker](https://github.com/stympy/faker). Also, most of+the combinators in this packages has been inspired (read as taken)+from the `fake` library. Also, `fakedata` offers fairly good amount of support of different locales. Also since we rely on an external data source, we get free updates and high quality data source with little effort. Also, it's easier to extend the library with [it's own data-source](https://github.com/psibi/fakedata/blob/master/Development.md#custom-fake-source-support-with-yml-file)-if we want to do it that way.+source](https://github.com/psibi/fakedata/blob/master/Development.md#custom-fake-source-support-with-yml-file) if we want to do it that way. -Acknowledgments----------------+# Acknowledgments -Benjamin Curtis for his [Ruby faker-library](https://github.com/stympy/faker) from which the data source is-taken from.+Benjamin Curtis for his [Ruby faker library](https://github.com/stympy/faker) from which the data+source is taken from. -Icons made by [Freepik](https://www.flaticon.com/authors/freepik) from-[Flaticon](https://www.flaticon.com/).+Icons made by [Freepik](https://www.flaticon.com/authors/freepik) from [Flaticon](https://www.flaticon.com/).
fakedata.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.5.+-- This file has been generated from package.yaml by hpack version 0.34.7. -- -- see: https://github.com/sol/hpack name: fakedata-version: 1.0.2+version: 1.0.3 synopsis: Library for producing fake data description: Please see the README on GitHub at <https://github.com/psibi/fakedata#readme> category: Random, Fake, FakeData
src/Faker.hs view
@@ -1,39 +1,46 @@-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} module Faker- (- -- * Types- Fake- , FakeT(.., Fake)- , FakerSettings- , FakerException(..)- , defaultFakerSettings+ ( -- * Types+ Fake,+ FakeT (.., Fake),+ FakerSettings,+ FakerException (..),+ NonDeterministicSeed (..),+ defaultFakerSettings,+ -- * Setters- , setLocale- , setRandomGen- , setDeterministic- , setNonDeterministic- , setCacheField- , setCacheFile- , replaceCacheField- , replaceCacheFile+ setLocale,+ setRandomGen,+ setDeterministic,+ setNonDeterministic,+ setNonDeterministicSeed,+ setCacheField,+ setCacheFile,+ replaceCacheField,+ replaceCacheFile,+ -- * Getters- , getRandomGen- , getLocale- , getDeterministic- , getCacheField- , getCacheFile+ getRandomGen,+ getLocale,+ getDeterministic,+ getNonDeterministicSeed,+ getCacheField,+ getCacheFile,+ -- * Generators- , generate- , generateNonDeterministic- , generateWithSettings- ) where+ generate,+ generateNonDeterministic,+ generateNonDeterministicWithFixedSeed,+ generateWithSettings,+ )+where import Control.Exception (Exception) import Control.Monad (ap)@@ -44,46 +51,55 @@ import Data.Text (Text) import Data.Typeable import Data.Vector (Vector)-import Data.Word (Word64) import Data.Yaml (Value)-import Faker.Internal.Types (CacheFieldKey, CacheFileKey, AesonKey)+import Faker.Internal.Types (AesonKey, CacheFieldKey, CacheFileKey) import System.Random (StdGen, mkStdGen, newStdGen, split) #if MIN_VERSION_aeson(2,0,0) import qualified Data.Aeson.Key as K #endif data FakerSettings = FakerSettings- { fslocale :: !Text -- ^ Locale settings for your fake data source.- , fsrandomGen :: !StdGen -- ^ Seed to initialize random generator state- , fsDeterministic :: !Bool -- ^ Controls whether you want- -- deterministic out. This overrides- -- 'fsrandomGen'.- , fsCacheField :: (IORef (HM.HashMap CacheFieldKey (Vector Text)))- , fsCacheFile :: (IORef (HM.HashMap CacheFileKey Value))+ { -- | Locale settings for your fake data source.+ fslocale :: !Text,+ -- | Seed to initialize random generator state+ fsrandomGen :: !StdGen,+ -- | Controls whether you want+ -- deterministic out. This overrides+ -- 'fsrandomGen'.+ fsDeterministic :: !Bool,+ fsNonDeterministicBehavior :: !NonDeterministicSeed,+ fsCacheField :: (IORef (HM.HashMap CacheFieldKey (Vector Text))),+ fsCacheFile :: (IORef (HM.HashMap CacheFileKey Value)) } newtype FakerGen = FakerGen { unFakerGen :: (Int, StdGen)- } deriving (Show)+ }+ deriving (Show) instance Show FakerSettings where show (FakerSettings {..}) = show fslocale ++ show fsrandomGen ++ show fsDeterministic data FakerException- = InvalidLocale String -- ^ This is thrown when it is not able to- -- find the fake data source for your- -- localization.- | InvalidField String- AesonKey -- ^ The 'String' represents the field it is- -- trying to resolve and the 'Key' field- -- is something you passed on.- | NoDataFound FakerSettings -- ^ This is thrown when you have no- -- data. This may likely happen for- -- locales other than `en`.- | ParseError String -- ^ This is thrown when the parsing step- -- fails. The 'String' represents the error- -- message.+ = -- | This is thrown when it is not able to+ -- find the fake data source for your+ -- localization.+ InvalidLocale String+ | -- | The 'String' represents the field it is+ -- trying to resolve and the 'Key' field+ -- is something you passed on.+ InvalidField+ String+ AesonKey+ | -- | This is thrown when you have no+ -- data. This may likely happen for+ -- locales other than `en`.+ NoDataFound FakerSettings+ | -- | This is thrown when the parsing step+ -- fails. The 'String' represents the error+ -- message.+ ParseError String deriving (Typeable, Show) instance Exception FakerException@@ -92,11 +108,12 @@ defaultFakerSettings :: FakerSettings defaultFakerSettings = FakerSettings- { fslocale = "en"- , fsrandomGen = mkStdGen 10000- , fsDeterministic = True- , fsCacheField = error "defaultFakerSettings: fsCacheField not initialized"- , fsCacheFile = error "defaultFakerSettings: fsCacheFile not initialized"+ { fslocale = "en",+ fsrandomGen = mkStdGen 10000,+ fsDeterministic = True,+ fsCacheField = error "defaultFakerSettings: fsCacheField not initialized",+ fsCacheFile = error "defaultFakerSettings: fsCacheFile not initialized",+ fsNonDeterministicBehavior = NewSeed } -- | Sets the locale. Note that for any other locale apart from@@ -120,6 +137,10 @@ getLocale :: FakerSettings -> Text getLocale FakerSettings {..} = fslocale +-- | Sets the 'NonDeterministicSeed'+setNonDeterministicSeed :: NonDeterministicSeed -> FakerSettings -> FakerSettings+setNonDeterministicSeed seed fs = fs { fsNonDeterministicBehavior = seed }+ -- | Set the output of fakedata to be deterministic. With this you -- will get the same ouput for the functions every time. --@@ -152,16 +173,21 @@ getDeterministic :: FakerSettings -> Bool getDeterministic FakerSettings {..} = fsDeterministic +-- | Get the 'NonDeterministicSeed' from faker settings. Note that+-- this setting is only applicable when use non deterministic output.+getNonDeterministicSeed :: FakerSettings -> NonDeterministicSeed+getNonDeterministicSeed FakerSettings {..} = fsNonDeterministicBehavior+ getCacheField :: FakerSettings -> IO (HM.HashMap CacheFieldKey (Vector Text)) getCacheField FakerSettings {..} = readIORef fsCacheField setCacheField ::- HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO ()+ HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO () setCacheField cache fs = do writeIORef (fsCacheField fs) cache replaceCacheField ::- HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO FakerSettings+ HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO FakerSettings replaceCacheField cache fs = do ref <- newIORef cache pure $ fs {fsCacheField = ref}@@ -173,7 +199,7 @@ setCacheFile cache fs = writeIORef (fsCacheFile fs) cache replaceCacheFile ::- HM.HashMap CacheFileKey Value -> FakerSettings -> IO FakerSettings+ HM.HashMap CacheFileKey Value -> FakerSettings -> IO FakerSettings replaceCacheFile cache fs = do ref <- newIORef cache pure $ fs {fsCacheFile = ref}@@ -184,7 +210,7 @@ -- | Fake data type. This is the type you will be using to produce -- fake values.-type Fake a = FakeT IO a+type Fake = FakeT IO pattern Fake :: (FakerSettings -> IO a) -> Fake a pattern Fake f = FakeT f@@ -197,10 +223,11 @@ fmap :: (a -> b) -> FakeT m a -> FakeT m b fmap f (FakeT h) = FakeT- (\r -> do- a <- h r- let b = f a- pure b)+ ( \r -> do+ a <- h r+ let b = f a+ pure b+ ) instance Monad m => Applicative (FakeT m) where {-# INLINE pure #-}@@ -217,15 +244,19 @@ f >>= k = generateNewFake f k generateNewFake :: Monad m => FakeT m a -> (a -> FakeT m b) -> FakeT m b-generateNewFake (FakeT h) k = FakeT (\settings -> do- let deterministic = getDeterministic settings- currentStdGen = getRandomGen settings- newStdGen = if deterministic- then currentStdGen- else fst $ split currentStdGen- item <- h settings- let (FakeT k1) = k item- k1 (setRandomGen newStdGen settings))+generateNewFake (FakeT h) k =+ FakeT+ ( \settings -> do+ let deterministic = getDeterministic settings+ currentStdGen = getRandomGen settings+ newStdGen =+ if deterministic+ then currentStdGen+ else fst $ split currentStdGen+ item <- h settings+ let (FakeT k1) = k item+ k1 (setRandomGen newStdGen settings)+ ) {-# SPECIALIZE INLINE generateNewFake :: Fake Text -> (Text -> Fake Text) -> Fake Text #-} instance MonadIO m => MonadIO (FakeT m) where@@ -241,7 +272,8 @@ mempty = pure mempty mappend mx my = mappend <$> mx <*> my --- | Generate fake value with 'defaultFakerSettings'+-- | Generate fake value with 'defaultFakerSettings'. This produces+-- deterministic output by default. -- -- @ -- λ> import qualified Faker.Name as FN@@ -281,8 +313,44 @@ stdGen <- if deterministic then pure $ getRandomGen settings- else liftIO newStdGen+ else case fsNonDeterministicBehavior settings of+ FixedSeed -> pure $ fst $ split (getRandomGen settings)+ NewSeed -> liftIO newStdGen let newSettings = setRandomGen stdGen settings cacheField <- liftIO $ newIORef HM.empty cacheFile <- liftIO $ newIORef HM.empty f $ newSettings {fsCacheField = cacheField, fsCacheFile = cacheFile}++-- | Generate fake value with 'NonDeterministicSeed' as+-- 'FixedSeed'. The difference between 'generateNonDeterministic' and+-- this function is that this uses a fixed seed set via `setRandomGen`+-- as it's initial seed value.+--+-- Executing this function multiple times will result in generation of+-- same values.+--+-- @since 1.0.3+-- @+-- λ> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+-- [98,87,77,33,98]+-- λ> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)+-- [98,87,77,33,98]+-- @+generateNonDeterministicWithFixedSeed :: MonadIO m => FakeT m a -> m a+generateNonDeterministicWithFixedSeed =+ generateWithSettings $+ setNonDeterministic+ ( defaultFakerSettings+ { fsNonDeterministicBehavior = FixedSeed+ }+ )++-- | NonDeterministicSeed type which controls if a fixed seed is going+-- to be used or if a new seed will be generated each time.+--+-- @since 1.0.3+data NonDeterministicSeed+ = -- | Always use a fixed seed.+ FixedSeed+ | -- | Use a new seed every time.+ NewSeed
test/CombinatorsSpec.hs view
@@ -42,3 +42,7 @@ it "orderedList" $ do item :: [Text] <- generate $ orderedList 5 country item `shouldSatisfy` (\x -> Prelude.length x == 5)+ it "works with FixedSeed" $ do+ items1 :: [Int] <- generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,50)+ items2 :: [Int] <- generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,50)+ items1 `shouldBe` items2