Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix quality and performance issues, add monadic API
Browse files Browse the repository at this point in the history
This patch is mostly backwards compatible. See "Breaking Changes" below
for the full list of backwards incompatible changes.

This patch fixes quality and performance issues, addresses additional
miscellaneous issues, and introduces a monadic API.

Issues addressed
================

Priority issues fixed in this patch:

- Title: "The seeds generated by split are not independent"
  Link:  haskell#25
  Fixed: changed algorithm to SplitMix, which provides a robust 'split'
  operation

- Title: "Very low throughput"
  Link:  haskell#51
  Fixed: see "Performance" below

Additional issues addressed in this patch:

- Title: "Add Random instances for tuples"
  Link:  haskell#26
  Addressed: added 'Uniform' instances for up to 6-tuples

- Title: "Add Random instance for Natural"
  Link:  haskell#44
  Addressed: added 'UniformRange' instance for 'Natural'

- Title: "incorrect distribution of randomR for floating-point numbers"
  Link:  haskell#53
  Addressed: see "Regarding floating-point numbers" below

- Title: "System/Random.hs:43:1: warning: [-Wtabs]"
  Link:  haskell#55
  Fixed: no more tabs

- Title: "Why does random for Float and Double produce exactly 24 or 53 bits?"
  Link:  haskell#58
  Fixed: see "Regarding floating-point numbers" below

- Title: "read :: StdGen fails for strings longer than 6"
  Link:  haskell#59
  Addressed: 'StdGen' is no longer an instance of 'Read'

Regarding floating-point numbers: with this patch, the relevant
instances for 'Float' and 'Double' sample more bits than before but do
not sample every possible representable value. The documentation now
clearly spells out what this means for users.

Quality (issue 25)
==================

The algorithm [1] in version 1.1 of this library fails empirical PRNG
tests when used to generate "split sequences" as proposed in [3].

SplitMix [2] passes the same tests. This patch changes 'StdGen' to use
the SplitMix implementation provided by the splitmix package.

Test batteries used: dieharder, TestU1, PractRand.

[1]: P. L'Ecuyer, "Efficient and portable combined random number
generators". https://doi.org/10.1145/62959.62969

[2]: G. L. Steele, D. Lea, C. H. Flood, "Fast splittable pseudorandom
number generators". https://doi.org/10.1145/2714064.2660195

[3]: H. G. Schaathun, "Evaluation of splittable pseudo-random
generators". https://doi.org/10.1017/S095679681500012X

Performance (issue 51)
======================

The "improvement" column in the following table is a multiplier: the
improvement for 'random' for type 'Float' is 1038, so this operation is
1038 times faster with this patch.

| Name                    | Mean (1.1) | Mean (patch) | Improvement|
| ----------------------- | ---------- | ------------ | ---------- |
| pure/random/Float       |         30 |         0.03 |        1038|
| pure/random/Double      |         52 |         0.03 |        1672|
| pure/random/Integer     |         43 |         0.33 |         131|
| pure/uniform/Word8      |         14 |         0.03 |         422|
| pure/uniform/Word16     |         13 |         0.03 |         375|
| pure/uniform/Word32     |         21 |         0.03 |         594|
| pure/uniform/Word64     |         42 |         0.03 |        1283|
| pure/uniform/Word       |         44 |         0.03 |        1491|
| pure/uniform/Int8       |         15 |         0.03 |         511|
| pure/uniform/Int16      |         15 |         0.03 |         507|
| pure/uniform/Int32      |         22 |         0.03 |         749|
| pure/uniform/Int64      |         44 |         0.03 |        1405|
| pure/uniform/Int        |         43 |         0.03 |        1512|
| pure/uniform/Char       |         17 |         0.49 |          35|
| pure/uniform/Bool       |         18 |         0.03 |         618|
| pure/uniform/CChar      |         14 |         0.03 |         485|
| pure/uniform/CSChar     |         14 |         0.03 |         455|
| pure/uniform/CUChar     |         13 |         0.03 |         448|
| pure/uniform/CShort     |         14 |         0.03 |         473|
| pure/uniform/CUShort    |         13 |         0.03 |         457|
| pure/uniform/CInt       |         21 |         0.03 |         737|
| pure/uniform/CUInt      |         21 |         0.03 |         742|
| pure/uniform/CLong      |         43 |         0.03 |        1544|
| pure/uniform/CULong     |         42 |         0.03 |        1460|
| pure/uniform/CPtrdiff   |         43 |         0.03 |        1494|
| pure/uniform/CSize      |         43 |         0.03 |        1475|
| pure/uniform/CWchar     |         22 |         0.03 |         785|
| pure/uniform/CSigAtomic |         21 |         0.03 |         749|
| pure/uniform/CLLong     |         43 |         0.03 |        1554|
| pure/uniform/CULLong    |         42 |         0.03 |        1505|
| pure/uniform/CIntPtr    |         43 |         0.03 |        1476|
| pure/uniform/CUIntPtr   |         42 |         0.03 |        1463|
| pure/uniform/CIntMax    |         43 |         0.03 |        1535|
| pure/uniform/CUIntMax   |         42 |         0.03 |        1493|

API changes
===========

StatefulGen
-----------

This patch adds a class 'StatefulGen':

    -- | 'StatefulGen' is an interface to monadic pseudo-random number generators.
    class Monad m => StatefulGen g m where
      uniformWord32 :: g -> m Word32 -- default implementation in terms of uniformWord64
      uniformWord64 :: g -> m Word64 -- default implementation in terms of uniformWord32
      -- plus methods for other word sizes and for byte strings
      -- all have default implementations so the MINIMAL pragma holds

In 'StatefulGen g m', 'g' is the type of the generator and 'm' the underlying
monad.

Four 'StatefulGen' instances ("monadic adapters") are provided for pure
generators to enable their use in monadic code. The documentation
describes them in detail.

FrozenGen
---------

This patch also introduces a class 'FrozenGen':

    -- | 'FrozenGen' is designed for stateful pseudo-random number generators
    -- that can be saved as and restored from an immutable data type.
    class StatefulGen (MutableGen f m) m => FrozenGen f m where
      type MutableGen f m = (g :: Type) | g -> f
      freezeGen :: MutableGen f m -> m f
      thawGen :: f -> m (MutableGen f m)

'f' is the type of the generator's state "at rest" and 'm' the underlying
monad. 'MutableGen' is defined as an injective type family via 'g -> f' so for
any generator 'g', the type 'f' of its at-rest state is well-defined.

Both 'StatefulGen' and 'FrozenGen' are generic enough to accommodate, for
example, the 'Gen' type from the 'mwc-random' package, which itself abstracts
over the underlying primitive monad and state token. The documentation shows
the full instances.

'Uniform' and 'UniformRange'
----------------------------

The 'Random' typeclass has conceptually been split into 'Uniform' and
'UniformRange'. The 'Random' typeclass is still included for backwards
compatibility. 'Uniform' is for types where it is possible to sample
from the type's entire domain; 'UniformRange' is for types where one can
sample from a specified range.

Breaking Changes
================

This patch introduces these breaking changes:

* requires 'base >= 4.10' (GHC-8.2)
* 'StdGen' is no longer an instance of 'Read'
* 'randomIO' and 'randomRIO' where extracted from the 'Random' class into
  separate functions

In addition, there may be import clashes with new functions, e.g. 'uniform' and
'uniformR'.

Deprecations
============

This patch introduces 'genWord64', 'genWord32' and similar methods to
the 'RandomGen' class. The significantly slower method 'next' and its
companion 'genRange' are now deprecated.

Co-authored-by: Alexey Kuleshevich <[email protected]>
Co-authored-by: idontgetoutmuch <[email protected]>
Co-authored-by: Leonhard Markert <[email protected]>
3 people committed Jun 22, 2020
1 parent cfdfe6f commit 64fa9bb
Showing 38 changed files with 3,930 additions and 1,576 deletions.
5 changes: 0 additions & 5 deletions .darcs-boring

This file was deleted.

15 changes: 3 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
*~

Thumbs.db
.DS_Store

GNUmakefile
dist-install/
ghc.mk

dist
.cabal-sandbox
cabal.sandbox.config
stack.yaml.lock
.stack-work/
cabal.project.local
197 changes: 192 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,192 @@
language: haskell
ghc:
- 7.4
- 7.6
- 7.8
# This Travis job script has been generated by a script via
#
# haskell-ci '--travis-patches' '.travis.yml.patch' 'random.cabal'
#
# To regenerate the script (for example after adjusting tested-with) run
#
# haskell-ci regenerate
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.10
#
version: ~> 1.0
language: c
os: linux
dist: xenial
git:
# whether to recursively clone submodules
submodules: false
cache:
directories:
- $HOME/.cabal/packages
- $HOME/.cabal/store
- $HOME/.hlint
- $HOME/.stack
- $TRAVIS_BUILD_DIR/.stack-work
before_cache:
- rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
# remove files that are regenerated by 'cabal update'
- rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
- rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
- rm -rfv $CABALHOME/packages/head.hackage
jobs:
include:
- compiler: ghc-8.10.1
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.10.1","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.8.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.2","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.8.1
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.1","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.6.5
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}}
env: COVERALLS_STACK_YAML="stack-coveralls.yaml"
os: linux
- compiler: ghc-8.6.4
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.4","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.6.3
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.3","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.4.4
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.4.4","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.4.3
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.4.3","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.2.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.2.2","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.0.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.0.2","cabal-install-3.2"]}}
os: linux
- compiler: ghc-7.10.3
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-7.10.3","cabal-install-3.2"]}}
os: linux
- compiler: ghc-7.10.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-7.10.2","cabal-install-3.2"]}}
os: linux
before_install:
- HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
- WITHCOMPILER="-w $HC"
- HADDOCK=$(echo "/opt/$CC/bin/haddock" | sed 's/-/\//')
- HCPKG="$HC-pkg"
- unset CC
- CABAL=/opt/ghc/bin/cabal
- CABALHOME=$HOME/.cabal
- export PATH="$CABALHOME/bin:$PATH"
- TOP=$(pwd)
- "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
- echo $HCNUMVER
- CABAL="$CABAL -vnormal+nowrap"
- set -o pipefail
- TEST=--enable-tests
- BENCH=--enable-benchmarks
- HEADHACKAGE=false
- rm -f $CABALHOME/config
- |
echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config
echo "remote-build-reporting: anonymous" >> $CABALHOME/config
echo "write-ghc-environment-files: always" >> $CABALHOME/config
echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config
echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config
echo "world-file: $CABALHOME/world" >> $CABALHOME/config
echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config
echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config
echo "installdir: $CABALHOME/bin" >> $CABALHOME/config
echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config
echo "store-dir: $CABALHOME/store" >> $CABALHOME/config
echo "install-dirs user" >> $CABALHOME/config
echo " prefix: $CABALHOME" >> $CABALHOME/config
echo "repository hackage.haskell.org" >> $CABALHOME/config
echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
# Download and unpack the stack executable
- mkdir -p ~/.local/bin
- |
if [ -n "${COVERALLS_STACK_YAML}" ]; then
if [ `uname` = "Darwin" ]
then
travis_retry curl --insecure -L https://get.haskellstack.org/stable/osx-x86_64.tar.gz | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin
else
travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
fi
fi
install:
- ${CABAL} --version
- echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
- |
echo "program-default-options" >> $CABALHOME/config
echo " ghc-options: $GHCJOBS +RTS -M6G -RTS" >> $CABALHOME/config
- cat $CABALHOME/config
- rm -fv cabal.project cabal.project.local cabal.project.freeze
- travis_retry ${CABAL} v2-update -v
# Generate cabal.project
- rm -rf cabal.project cabal.project.local cabal.project.freeze
- touch cabal.project
- |
echo "packages: ." >> cabal.project
- if [ $HCNUMVER -ge 80200 ] ; then echo 'package random' >> cabal.project ; fi
- "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi"
- |
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(random)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
- if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
- ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH}
- "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
- rm cabal.project.freeze
- travis_wait 40 ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all
- travis_wait 40 ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all
script:
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
# Coverage
- |
if [ -n "${COVERALLS_STACK_YAML}" ]; then
stack --stack-yaml $COVERALLS_STACK_YAML test --coverage
stack --stack-yaml $COVERALLS_STACK_YAML hpc report --all
travis_retry curl -L https://github.com/lehins/stack-hpc-coveralls/releases/download/0.0.5.0/shc.tar.gz | tar xz shc
STACK_YAML="$COVERALLS_STACK_YAML" ./shc combined custom
fi
# Packaging...
- ${CABAL} v2-sdist all
# Unpacking...
- mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
- cd ${DISTDIR} || false
- find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
- find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
- PKGDIR_random="$(find . -maxdepth 1 -type d -regex '.*/random-[0-9.]*')"
# Generate cabal.project
- rm -rf cabal.project cabal.project.local cabal.project.freeze
- touch cabal.project
- |
echo "packages: ${PKGDIR_random}" >> cabal.project
- if [ $HCNUMVER -ge 80200 ] ; then echo 'package random' >> cabal.project ; fi
- "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi"
- |
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(random)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
# Building...
# this builds all libraries and executables (without tests/benchmarks)
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
# Building with tests and benchmarks...
# build & run tests, build benchmarks
- ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all
# Testing...
- ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all
# cabal check...
- (cd ${PKGDIR_random} && ${CABAL} -vnormal check)
# haddock...
- ${CABAL} v2-haddock $WITHCOMPILER --with-haddock $HADDOCK ${TEST} ${BENCH} all
# Building without installed constraints for packages in global-db...
- rm -f cabal.project.local
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all

# REGENDATA ("0.10",["--travis-patches",".travis.yml.patch","random.cabal"])
# EOF
50 changes: 50 additions & 0 deletions .travis.yml.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@@ -22,6 +22,8 @@ cache:
- $HOME/.cabal/packages
- $HOME/.cabal/store
- $HOME/.hlint
+ - $HOME/.stack
+ - $TRAVIS_BUILD_DIR/.stack-work
before_cache:
- rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
# remove files that are regenerated by 'cabal update'
@@ -44,6 +49,7 @@ jobs:
os: linux
- compiler: ghc-8.6.5
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}}
+ env: COVERALLS_STACK_YAML="stack-coveralls.yaml"
os: linux
- compiler: ghc-8.6.4
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.4","cabal-install-3.2"]}}
@@ -103,6 +109,17 @@ before_install:
echo " prefix: $CABALHOME" >> $CABALHOME/config
echo "repository hackage.haskell.org" >> $CABALHOME/config
echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
+ # Download and unpack the stack executable
+ - mkdir -p ~/.local/bin
+ - |
+ if [ -n "${COVERALLS_STACK_YAML}" ]; then
+ if [ `uname` = "Darwin" ]
+ then
+ travis_retry curl --insecure -L https://get.haskellstack.org/stable/osx-x86_64.tar.gz | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin
+ else
+ travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
+ fi
+ fi
install:
- ${CABAL} --version
- echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
@@ -131,6 +148,14 @@ install:
- travis_wait 40 ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all
script:
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+ # Coverage
+ - |
+ if [ -n "${COVERALLS_STACK_YAML}" ]; then
+ stack --stack-yaml $COVERALLS_STACK_YAML test --coverage
+ stack --stack-yaml $COVERALLS_STACK_YAML hpc report --all
+ travis_retry curl -L https://github.com/lehins/stack-hpc-coveralls/releases/download/0.0.5.0/shc.tar.gz | tar xz shc
+ STACK_YAML="$COVERALLS_STACK_YAML" ./shc combined custom
+ fi
# Packaging...
- ${CABAL} v2-sdist all
# Unpacking...
142 changes: 0 additions & 142 deletions Benchmark/BinSearch.hs

This file was deleted.

24 changes: 0 additions & 24 deletions Benchmark/Makefile

This file was deleted.

322 changes: 0 additions & 322 deletions Benchmark/SimpleRNGBench.hs

This file was deleted.

82 changes: 81 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,84 @@
# 1.2.0

1. Breaking change which mostly maintains backwards compatibility, see
"Breaking Changes" below.
2. Support for monadic generators e.g. [mwc-random](https://hackage.haskell.org/package/mwc-random).
3. Monadic adapters for pure generators (providing a uniform monadic
interface to pure and monadic generators).
4. Faster in all cases except one by more than x18 (N.B. x18 not 18%) and
some cases (depending on the type) faster by more than x1000 - see
below for benchmarks.
5. Passes a large number of random number test suites:
* [dieharder](http://webhome.phy.duke.edu/~rgb/General/dieharder.php "venerable")
* [TestU01 (SmallCrush, Crush, BigCrush)](http://simul.iro.umontreal.ca/testu01/tu01.html "venerable")
* [PractRand](http://pracrand.sourceforge.net/ "active")
* [gjrand](http://gjrand.sourceforge.net/ "active")
* See [random-quality](https://github.com/tweag/random-quality)
for details on how to do this yourself.
6. Better quality split as judged by these
[tests](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/evaluation-of-splittable-pseudorandom-generators/3EBAA9F14939C5BB5560E32D1A132637). Again
see [random-quality](https://github.com/tweag/random-quality) for
details on how to do this yourself.
7. Unbiased generation of ranges.
8. Updated tests and benchmarks.
9. [Continuous integration](https://travis-ci.org/github/haskell/random).

### Breaking Changes

Version 1.2.0 introduces these breaking changes:

* requires `base >= 4.8` (GHC-7.10)
* `StdGen` is no longer an instance of `Read`
* `randomIO` and `randomRIO` were extracted from the `Random` class into
separate functions

In addition, there may be import clashes with new functions, e.g. `uniform` and
`uniformR`.

### Deprecations

Version 1.2.0 introduces `genWord64`, `genWord32` and similar methods to the
`RandomGen` class. The significantly slower method `next` and its companion
`genRange` are now deprecated.

### Issues Addressed

Issue Number | Description | Comment
--------------|-------------|--------
[25](https://github.com/haskell/random/issues/25) | The seeds generated by split are not independent | Fixed: changed algorithm to SplitMix, which provides a robust split operation
[26](https://github.com/haskell/random/issues/26) | Add Random instances for tuples | Addressed: added `Uniform` instances for up to 6-tuples
[44](https://github.com/haskell/random/issues/44) | Add Random instance for Natural | Addressed: added UniformRange instance for Natural
[51](https://github.com/haskell/random/issues/51) | Very low throughput | Fixed: see benchmarks below
[53](https://github.com/haskell/random/issues/53) | incorrect distribution of randomR for floating-point numbers | (\*)
[55](https://github.com/haskell/random/issues/55) | System/Random.hs:43:1: warning: [-Wtabs] | Fixed: No more tabs
[58](https://github.com/haskell/random/issues/58) | Why does random for Float and Double produce exactly 24 or 53 bits? | (\*)
[59](https://github.com/haskell/random/issues/59) | read :: StdGen fails for strings longer than 6 | Addressed: StdGen is no longer an instance of Read

#### Comments

(\*) 1.2 samples more bits but does not sample every `Float` or
`Double`. There are methods to do this but they have some downsides;
see [here](https://github.com/idontgetoutmuch/random/issues/105) for a
fuller discussion.

## Benchmarks

Here are some benchmarks run on a 3.1 GHz Intel Core i7. The full
benchmarks can be run using e.g. `stack bench`. The benchmarks are
measured in milliseconds per 100,000 generations. In some cases, the
performance is over x1000 times better; the minimum performance
increase for the types listed below is more than x36.

Name | 1.1 Mean | 1.2 Mean
------------|----------|----------
Float | 27.819 | 0.305
Double | 50.644 | 0.328
Integer | 42.332 | 0.332
Word | 40.739 | 0.027
Int | 43.847 | 0.028
Char | 17.009 | 0.462
Bool | 17.542 | 0.027

# 1.1
* breaking change to `randomIValInteger` to improve RNG quality and performance
see https://github.com/haskell/random/pull/4 and
@@ -23,4 +104,3 @@ bump for bug fixes,

# 1.0.0.4
bumped version for float/double range bugfix

196 changes: 0 additions & 196 deletions DEVLOG.md

This file was deleted.

19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
The Haskell Standard Library -- Random Number Generation
========================================================
[![Build Status](https://secure.travis-ci.org/haskell/random.svg?branch=master)](http://travis-ci.org/haskell/random)
# The Haskell Standard Library

## Random Number Generation

### Status

| Language | Travis | Coveralls |
|:--------:|:------:|:---------:|
| ![GitHub top language](https://img.shields.io/github/languages/top/idontgetoutmuch/random.svg) | [![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?v1.2-proposal)](http://travis-ci.org/idontgetoutmuch/random) | [![Coverage Status](https://coveralls.io/repos/github/idontgetoutmuch/random/badge.svg?branch=v1.2-proposal)](https://coveralls.io/github/idontgetoutmuch/random?branch=v1.2-proposal)

| Package | Hackage | Nightly | LTS |
|:-------------------|:-------:|:-------:|:---:|
| [`random`](https://github.com/idontgetoutmuch/random)| [![Hackage](https://img.shields.io/hackage/v/random.svg)](https://hackage.haskell.org/package/random)| [![Nightly](https://www.stackage.org/package/random/badge/nightly)](https://www.stackage.org/nightly/package/random)| [![Nightly](https://www.stackage.org/package/random/badge/lts)](https://www.stackage.org/lts/package/random)


### Description

This library provides a basic interface for (splittable) random number generators.

2 changes: 0 additions & 2 deletions Setup.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
module Main (main) where

import Distribution.Simple

main :: IO ()
609 changes: 0 additions & 609 deletions System/Random.hs

This file was deleted.

149 changes: 149 additions & 0 deletions bench-legacy/BinSearch.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

{-
Binary search over benchmark input sizes.
There are many good ways to measure the time it takes to perform a
certain computation on a certain input. However, frequently, it's
challenging to pick the right input size for all platforms and all
compilataion modes.
Sometimes for linear-complexity benchmarks it is better to measure
/throughput/, i.e. elements processed per second. That is, fixing
the time of execution and measuring the amount of work done (rather
than the reverse). This library provides a simple way to search for
an appropriate input size that results in the desired execution time.
An alternative approach is to kill the computation after a certain
amount of time and observe how much work it has completed.
-}
module BinSearch
(
binSearch
)
where

import Control.Monad
import Data.Time.Clock -- Not in 6.10
import Data.List
import System.IO
import Prelude hiding (min,max,log)



-- | Binary search for the number of inputs to a computation that
-- results in a specified amount of execution time in seconds. For example:
--
-- > binSearch verbose N (min,max) kernel
--
-- ... will find the right input size that results in a time
-- between min and max, then it will then run for N trials and
-- return the median (input,time-in-seconds) pair.
binSearch :: Bool -> Integer -> (Double,Double) -> (Integer -> IO ()) -> IO (Integer, Double)
binSearch verbose trials (min, max) kernel = do
when verbose $
putStrLn $
"[binsearch] Binary search for input size resulting in time in range " ++
show (min, max)
let desired_exec_length = 1.0
good_trial t =
(toRational t <= toRational max) && (toRational t >= toRational min)
-- At some point we must give up...
loop n
| n > ((2 :: Integer) ^ (100 :: Integer)) =
error
"ERROR binSearch: This function doesn't seem to scale in proportion to its last argument."
-- Not allowed to have "0" size input, bump it back to one:
loop 0 = loop 1
loop n = do
when verbose $ putStr $ "[binsearch:" ++ show n ++ "] "
time <- timeit $ kernel n
when verbose $ putStrLn $ "Time consumed: " ++ show time
let rate = fromIntegral n / time
-- [2010.06.09] Introducing a small fudge factor to help our guess get over the line:
let initial_fudge_factor = 1.10
fudge_factor = 1.01 -- Even in the steady state we fudge a little
guess = desired_exec_length * rate
-- TODO: We should keep more history here so that we don't re-explore input space we
-- have already explored. This is a balancing act because of randomness in
-- execution time.
if good_trial time
then do
when verbose $
putStrLn
"[binsearch] Time in range. LOCKING input size and performing remaining trials."
print_trial 1 n time
lockin (trials - 1) n [time]
else if time < 0.100
then loop (2 * n)
else do
when verbose $
putStrLn $
"[binsearch] Estimated rate to be " ++
show (round rate :: Integer) ++
" per second. Trying to scale up..."
-- Here we've exited the doubling phase, but we're making our
-- first guess as to how big a real execution should be:
if time > 0.100 && time < 0.33 * desired_exec_length
then do
when verbose $
putStrLn
"[binsearch] (Fudging first guess a little bit extra)"
loop (round $ guess * initial_fudge_factor)
else loop (round $ guess * fudge_factor)
-- Termination condition: Done with all trials.
lockin 0 n log = do
when verbose $
putStrLn $
"[binsearch] Time-per-unit for all trials: " ++
concat
(intersperse " " (map (show . (/ toDouble n) . toDouble) $ sort log))
return (n, log !! (length log `quot` 2)) -- Take the median
lockin trials_left n log = do
when verbose $
putStrLn
"[binsearch]------------------------------------------------------------"
time <- timeit $ kernel n
-- hFlush stdout
print_trial (trials - trials_left + 1) n time
-- whenverbose$ hFlush stdout
lockin (trials_left - 1) n (time : log)
print_trial :: Integer -> Integer -> NominalDiffTime -> IO ()
print_trial trialnum n time =
let rate = fromIntegral n / time
timeperunit = time / fromIntegral n
in when verbose $
putStrLn $
"[binsearch] TRIAL: " ++
show trialnum ++
" secPerUnit: " ++
showTime timeperunit ++
" ratePerSec: " ++ show rate ++ " seconds: " ++ showTime time
(n, t) <- loop 1
return (n, fromRational $ toRational t)


showTime :: NominalDiffTime -> String
showTime t = show ((fromRational $ toRational t) :: Double)

toDouble :: Real a => a -> Double
toDouble = fromRational . toRational


-- Could use cycle counters here.... but the point of this is to time
-- things on the order of a second.
timeit :: IO () -> IO NominalDiffTime
timeit io = do
strt <- getCurrentTime
io
end <- getCurrentTime
return (diffUTCTime end strt)
{-
test :: IO (Integer,Double)
test =
binSearch True 3 (1.0, 1.05)
(\n ->
do v <- newIORef 0
forM_ [1..n] $ \i -> do
old <- readIORef v
writeIORef v (old+i))
-}
269 changes: 269 additions & 0 deletions bench-legacy/SimpleRNGBench.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
{-# LANGUAGE BangPatterns, ScopedTypeVariables, ForeignFunctionInterface #-}
{-# OPTIONS_GHC -fwarn-unused-imports #-}

-- | A simple script to do some very basic timing of the RNGs.

module Main where

import System.Exit (exitSuccess, exitFailure)
import System.Environment
import System.Random
import System.CPUTime (getCPUTime)
import System.CPUTime.Rdtsc
import System.Console.GetOpt

import GHC.Conc
import Control.Concurrent
import Control.Monad
import Control.Exception

import Data.IORef
import Data.Word
import Data.List hiding (last,sum)
import Data.Int
import Data.List.Split hiding (split)
import Text.Printf

import Foreign.Ptr
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Storable (peek,poke)

import Prelude hiding (last,sum)
import BinSearch

----------------------------------------------------------------------------------------------------
-- Miscellaneous helpers:

-- Readable large integer printing:
commaint :: Show a => a -> String
commaint n = reverse $ concat $ intersperse "," $ chunk 3 $ reverse (show n)

padleft :: Int -> String -> String
padleft n str | length str >= n = str
padleft n str | otherwise = take (n - length str) (repeat ' ') ++ str

padright :: Int -> String -> String
padright n str | length str >= n = str
padright n str | otherwise = str ++ take (n - length str) (repeat ' ')

fmt_num :: (RealFrac a, PrintfArg a) => a -> String
fmt_num n =
if n < 100
then printf "%.2f" n
else commaint (round n :: Integer)


-- Measure clock frequency, spinning rather than sleeping to try to
-- stay on the same core.
measureFreq :: IO Int64
measureFreq = do
let second = 1000 * 1000 * 1000 * 1000 -- picoseconds are annoying
t1 <- rdtsc
start <- getCPUTime
let loop !n !last = do
t2 <- rdtsc
when (t2 < last) $ putStrLn $ "COUNTERS WRAPPED " ++ show (last, t2)
cput <- getCPUTime
if cput - start < second
then loop (n + 1) t2
else return (n, t2)
(n, t2) <- loop 0 t1
putStrLn $ " Approx getCPUTime calls per second: " ++ commaint (n :: Int64)
when (t2 < t1) $
putStrLn $
"WARNING: rdtsc not monotonically increasing, first " ++
show t1 ++ " then " ++ show t2 ++ " on the same OS thread"
return $ fromIntegral (t2 - t1)

----------------------------------------------------------------------------------------------------

-- Test overheads without actually generating any random numbers:
data NoopRNG = NoopRNG
instance RandomGen NoopRNG where
next g = (0, g)
genRange _ = (0, 0)
split g = (g, g)

-- An RNG generating only 0 or 1:
data BinRNG = BinRNG StdGen
instance RandomGen BinRNG where
next (BinRNG g) = (x `mod` 2, BinRNG g')
where
(x, g') = next g
genRange _ = (0, 1)
split (BinRNG g) = (BinRNG g1, BinRNG g2)
where
(g1, g2) = split g


----------------------------------------------------------------------------------------------------
-- Drivers to get random numbers repeatedly.

type Kern = Int -> Ptr Int -> IO ()

-- [2011.01.28] Changing this to take "count" and "accumulator ptr" as arguments:
-- foreign import ccall "cbits/c_test.c" blast_rands :: Kern
-- foreign import ccall "cbits/c_test.c" store_loop :: Kern
-- foreign import ccall unsafe "stdlib.hs" rand :: IO Int

{-# INLINE timeit #-}
timeit :: (Random a, RandomGen g) => Int -> Int64 -> String -> g -> (g -> (a,g)) -> IO ()
timeit numthreads freq msg gen nxt = do
counters <- forM [1 .. numthreads] (const $ newIORef (1 :: Int64))
tids <- forM counters $ \counter -> forkIO $ infloop counter (nxt gen)
threadDelay (1000 * 1000) -- One second
mapM_ killThread tids
finals <- mapM readIORef counters
let mean :: Double =
fromIntegral (foldl1 (+) finals) / fromIntegral numthreads
cycles_per :: Double = fromIntegral freq / mean
printResult (round mean :: Int64) msg cycles_per
where
infloop !counter (!_, !g) = do
incr counter
infloop counter (nxt g)
incr !counter
-- modifyIORef counter (+1) -- Not strict enough!
= do
c <- readIORef counter
let c' = c + 1
_ <- evaluate c'
writeIORef counter c'


-- This function times an IO function on one or more threads. Rather
-- than running a fixed number of iterations, it uses a binary search
-- to find out how many iterations can be completed in a second.
timeit_foreign :: Int -> Int64 -> String -> (Int -> Ptr Int -> IO ()) -> IO Int64
timeit_foreign numthreads freq msg ffn = do
ptr :: ForeignPtr Int <- mallocForeignPtr
let kern =
if numthreads == 1
then ffn
else replicate_kernel numthreads ffn
wrapped n = withForeignPtr ptr (kern $ fromIntegral n)
(n, t) <- binSearch False 1 (1.0, 1.05) wrapped
let total_per_second = round $ fromIntegral n * (1 / t)
cycles_per = fromIntegral freq * t / fromIntegral n
printResult total_per_second msg cycles_per
return total_per_second
-- This lifts a C kernel to operate simultaneously on N threads.
where
replicate_kernel :: Int -> Kern -> Kern
replicate_kernel nthreads kern n ptr = do
ptrs <- forM [1 .. nthreads] (const mallocForeignPtr)
tmpchan <- newChan
-- let childwork = ceiling$ fromIntegral n / fromIntegral nthreads
let childwork = n -- Keep it the same.. interested in per-thread throughput.
-- Fork/join pattern:
forM_ ptrs $ \pt ->
forkIO $
withForeignPtr pt $ \p -> do
kern (fromIntegral childwork) p
result <- peek p
writeChan tmpchan result
results <- forM [1 .. nthreads] $ \_ -> readChan tmpchan
-- Meaningless semantics here... sum the child ptrs and write to the input one:
poke ptr (foldl1 (+) results)


printResult :: Int64 -> String -> Double -> IO ()
printResult total msg cycles_per =
putStrLn $
" " ++
padleft 11 (commaint total) ++
" randoms generated " ++
padright 27 ("[" ++ msg ++ "]") ++
" ~ " ++ fmt_num cycles_per ++ " cycles/int"

----------------------------------------------------------------------------------------------------
-- Main Script

data Flag = NoC | Help
deriving (Show, Eq)

options :: [OptDescr Flag]
options =
[ Option ['h'] ["help"] (NoArg Help) "print program help"
, Option [] ["noC"] (NoArg NoC) "omit C benchmarks, haskell only"
]

main :: IO ()
main = do
argv <- getArgs
let (opts,_,other) = getOpt Permute options argv

unless (null other) $ do
putStrLn "ERROR: Unrecognized options: "
mapM_ putStr other
exitFailure

when (Help `elem` opts) $ do
putStr $ usageInfo "Benchmark random number generation" options
exitSuccess

putStrLn "\nHow many random numbers can we generate in a second on one thread?"

t1 <- rdtsc
t2 <- rdtsc
putStrLn (" Cost of rdtsc (ffi call): " ++ show (t2 - t1))

freq <- measureFreq
putStrLn $ " Approx clock frequency: " ++ commaint freq

let randInt = random :: RandomGen g => g -> (Int,g)
randWord16 = random :: RandomGen g => g -> (Word16,g)
randFloat = random :: RandomGen g => g -> (Float,g)
randCFloat = random :: RandomGen g => g -> (CFloat,g)
randDouble = random :: RandomGen g => g -> (Double,g)
randCDouble = random :: RandomGen g => g -> (CDouble,g)
randInteger = random :: RandomGen g => g -> (Integer,g)
randBool = random :: RandomGen g => g -> (Bool,g)
randChar = random :: RandomGen g => g -> (Char,g)

gen = mkStdGen 23852358661234
gamut th = do
putStrLn " First, timing System.Random.next:"
timeit th freq "constant zero gen" NoopRNG next
timeit th freq "System.Random stdGen/next" gen next

putStrLn "\n Second, timing System.Random.random at different types:"
timeit th freq "System.Random Ints" gen randInt
timeit th freq "System.Random Word16" gen randWord16
timeit th freq "System.Random Floats" gen randFloat
timeit th freq "System.Random CFloats" gen randCFloat
timeit th freq "System.Random Doubles" gen randDouble
timeit th freq "System.Random CDoubles" gen randCDouble
timeit th freq "System.Random Integers" gen randInteger
timeit th freq "System.Random Bools" gen randBool
timeit th freq "System.Random Chars" gen randChar

putStrLn "\n Next timing range-restricted System.Random.randomR:"
timeit th freq "System.Random Ints" gen (randomR (-100, 100::Int))
timeit th freq "System.Random Word16s" gen (randomR ( 100, 300::Word16))
timeit th freq "System.Random Floats" gen (randomR (-100, 100::Float))
timeit th freq "System.Random CFloats" gen (randomR (-100, 100::CFloat))
timeit th freq "System.Random Doubles" gen (randomR (-100, 100::Double))
timeit th freq "System.Random CDoubles" gen (randomR (-100, 100::CDouble))
timeit th freq "System.Random Integers" gen (randomR (-100, 100::Integer))
timeit th freq "System.Random Bools" gen (randomR (False, True::Bool))
timeit th freq "System.Random Chars" gen (randomR ('a', 'z'))
timeit th freq "System.Random BIG Integers" gen (randomR (0, (2::Integer) ^ (5000::Int)))

-- when (not$ NoC `elem` opts) $ do
-- putStrLn$ " Comparison to C's rand():"
-- timeit_foreign th freq "ptr store in C loop" store_loop
-- timeit_foreign th freq "rand/store in C loop" blast_rands
-- timeit_foreign th freq "rand in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> rand )
-- timeit_foreign th freq "rand/store in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> do n <- rand; poke ptr n )
-- return ()

-- Test with 1 thread and numCapabilities threads:
gamut 1
when (numCapabilities > 1) $ do
putStrLn $ "\nNow "++ show numCapabilities ++" threads, reporting mean randoms-per-second-per-thread:"
void $ gamut numCapabilities

putStrLn "Finished."

309 changes: 309 additions & 0 deletions bench/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where

import Control.Monad
import Control.Monad.State.Strict
import Data.Int
import Data.Proxy
import Data.Typeable
import Data.Word
import Foreign.C.Types
import Gauge.Main
import Numeric.Natural (Natural)
import System.Random.SplitMix as SM

import System.Random.Stateful

main :: IO ()
main = do
let !sz = 100000
genLengths =
-- create 5000 small lengths that are needed for ShortByteString generation
runStateGen (mkStdGen 2020) $ \g -> replicateM 5000 (uniformRM (16 + 1, 16 + 7) g)
defaultMain
[ bgroup "baseline"
[ let !smGen = SM.mkSMGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 smGen) sz
, let !smGen = SM.mkSMGen 1337 in bench "nextWord64" $ nf (genMany SM.nextWord64 smGen) sz
, let !smGen = SM.mkSMGen 1337 in bench "nextInt" $ nf (genMany SM.nextInt smGen) sz
, let !smGen = SM.mkSMGen 1337 in bench "split" $ nf (genMany SM.splitSMGen smGen) sz
]
, bgroup "pure"
[ bgroup "random"
[ pureRandomBench (Proxy :: Proxy Float) sz
, pureRandomBench (Proxy :: Proxy Double) sz
, pureRandomBench (Proxy :: Proxy Integer) sz
]
, bgroup "uniform"
[ pureUniformBench (Proxy :: Proxy Word8) sz
, pureUniformBench (Proxy :: Proxy Word16) sz
, pureUniformBench (Proxy :: Proxy Word32) sz
, pureUniformBench (Proxy :: Proxy Word64) sz
, pureUniformBench (Proxy :: Proxy Word) sz
, pureUniformBench (Proxy :: Proxy Int8) sz
, pureUniformBench (Proxy :: Proxy Int16) sz
, pureUniformBench (Proxy :: Proxy Int32) sz
, pureUniformBench (Proxy :: Proxy Int64) sz
, pureUniformBench (Proxy :: Proxy Int) sz
, pureUniformBench (Proxy :: Proxy Char) sz
, pureUniformBench (Proxy :: Proxy Bool) sz
, pureUniformBench (Proxy :: Proxy CChar) sz
, pureUniformBench (Proxy :: Proxy CSChar) sz
, pureUniformBench (Proxy :: Proxy CUChar) sz
, pureUniformBench (Proxy :: Proxy CShort) sz
, pureUniformBench (Proxy :: Proxy CUShort) sz
, pureUniformBench (Proxy :: Proxy CInt) sz
, pureUniformBench (Proxy :: Proxy CUInt) sz
, pureUniformBench (Proxy :: Proxy CLong) sz
, pureUniformBench (Proxy :: Proxy CULong) sz
, pureUniformBench (Proxy :: Proxy CPtrdiff) sz
, pureUniformBench (Proxy :: Proxy CSize) sz
, pureUniformBench (Proxy :: Proxy CWchar) sz
, pureUniformBench (Proxy :: Proxy CSigAtomic) sz
, pureUniformBench (Proxy :: Proxy CLLong) sz
, pureUniformBench (Proxy :: Proxy CULLong) sz
, pureUniformBench (Proxy :: Proxy CIntPtr) sz
, pureUniformBench (Proxy :: Proxy CUIntPtr) sz
, pureUniformBench (Proxy :: Proxy CIntMax) sz
, pureUniformBench (Proxy :: Proxy CUIntMax) sz
]
, bgroup "uniformR"
[ bgroup "full"
[ pureUniformRFullBench (Proxy :: Proxy Word8) sz
, pureUniformRFullBench (Proxy :: Proxy Word16) sz
, pureUniformRFullBench (Proxy :: Proxy Word32) sz
, pureUniformRFullBench (Proxy :: Proxy Word64) sz
, pureUniformRFullBench (Proxy :: Proxy Word) sz
, pureUniformRFullBench (Proxy :: Proxy Int8) sz
, pureUniformRFullBench (Proxy :: Proxy Int16) sz
, pureUniformRFullBench (Proxy :: Proxy Int32) sz
, pureUniformRFullBench (Proxy :: Proxy Int64) sz
, pureUniformRFullBench (Proxy :: Proxy Int) sz
, pureUniformRFullBench (Proxy :: Proxy Char) sz
, pureUniformRFullBench (Proxy :: Proxy Bool) sz
, pureUniformRFullBench (Proxy :: Proxy CChar) sz
, pureUniformRFullBench (Proxy :: Proxy CSChar) sz
, pureUniformRFullBench (Proxy :: Proxy CUChar) sz
, pureUniformRFullBench (Proxy :: Proxy CShort) sz
, pureUniformRFullBench (Proxy :: Proxy CUShort) sz
, pureUniformRFullBench (Proxy :: Proxy CInt) sz
, pureUniformRFullBench (Proxy :: Proxy CUInt) sz
, pureUniformRFullBench (Proxy :: Proxy CLong) sz
, pureUniformRFullBench (Proxy :: Proxy CULong) sz
, pureUniformRFullBench (Proxy :: Proxy CPtrdiff) sz
, pureUniformRFullBench (Proxy :: Proxy CSize) sz
, pureUniformRFullBench (Proxy :: Proxy CWchar) sz
, pureUniformRFullBench (Proxy :: Proxy CSigAtomic) sz
, pureUniformRFullBench (Proxy :: Proxy CLLong) sz
, pureUniformRFullBench (Proxy :: Proxy CULLong) sz
, pureUniformRFullBench (Proxy :: Proxy CIntPtr) sz
, pureUniformRFullBench (Proxy :: Proxy CUIntPtr) sz
, pureUniformRFullBench (Proxy :: Proxy CIntMax) sz
, pureUniformRFullBench (Proxy :: Proxy CUIntMax) sz
]
, bgroup "excludeMax"
[ pureUniformRExcludeMaxBench (Proxy :: Proxy Word8) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Word16) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Word32) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Word64) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Word) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Int8) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Int16) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Int32) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Int64) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Int) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Char) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy Bool) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CChar) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CSChar) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CUChar) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CShort) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CUShort) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CInt) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CUInt) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CLong) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CULong) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CPtrdiff) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CSize) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CWchar) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CSigAtomic) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CLLong) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CULLong) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CIntPtr) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CUIntPtr) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CIntMax) sz
, pureUniformRExcludeMaxBench (Proxy :: Proxy CUIntMax) sz
]
, bgroup "includeHalf"
[ pureUniformRIncludeHalfBench (Proxy :: Proxy Word8) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Word16) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Word32) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Word64) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Word) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Int8) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Int16) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Int32) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Int64) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy Int) sz
, pureUniformRIncludeHalfEnumBench (Proxy :: Proxy Char) sz
, pureUniformRIncludeHalfEnumBench (Proxy :: Proxy Bool) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CChar) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CSChar) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CUChar) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CShort) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CUShort) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CInt) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CUInt) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CLong) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CULong) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CPtrdiff) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CSize) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CWchar) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CSigAtomic) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CLLong) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CULLong) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CIntPtr) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CUIntPtr) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CIntMax) sz
, pureUniformRIncludeHalfBench (Proxy :: Proxy CUIntMax) sz
]
, bgroup "unbounded"
[ pureUniformRBench (Proxy :: Proxy Float) (1.23e-4, 5.67e8) sz
, pureUniformRBench (Proxy :: Proxy Double) (1.23e-4, 5.67e8) sz
, let !i = (10 :: Integer) ^ (100 :: Integer)
!range = (-i - 1, i + 1)
in pureUniformRBench (Proxy :: Proxy Integer) range sz
, let !n = (10 :: Natural) ^ (100 :: Natural)
!range = (1, n - 1)
in pureUniformRBench (Proxy :: Proxy Natural) range sz
]
, bgroup "floating"
[ bgroup "IO"
[ bench "uniformFloat01M" $ nfIO $ runStateGenT_ (mkStdGen 1337) $ \g ->
replicateM_ sz $ do !_ <- uniformFloat01M g
return ()
, bench "uniformFloatPositive01M" $ nfIO $ runStateGenT_ (mkStdGen 1337) $ \g ->
replicateM_ sz $ do !_ <- uniformFloatPositive01M g
return ()
, bench "uniformDouble01M" $ nfIO $ runStateGenT_ (mkStdGen 1337) $ \g ->
replicateM_ sz $ do !_ <- uniformDouble01M g
return ()
, bench "uniformDoublePositive01M" $ nfIO $ runStateGenT_ (mkStdGen 1337) $ \g ->
replicateM_ sz $ do !_ <- uniformDoublePositive01M g
return ()
]
--
, bgroup "St"
[ bench "uniformFloat01M" $ nf
(\n -> runStateGen_ (mkStdGen 1337) $ \g -> replicateM_ n $ do !_ <- uniformFloat01M g
return ()
) sz
, bench "uniformFloatPositive01M" $ nf
(\n -> runStateGen_ (mkStdGen 1337) $ \g -> replicateM_ n $ do !_ <- uniformFloatPositive01M g
return ()
) sz
, bench "uniformDouble01M" $ nf
(\n -> runStateGen_ (mkStdGen 1337) $ \g -> replicateM_ n $ do !_ <- uniformDouble01M g
return ()
) sz
, bench "uniformDoublePositive01M" $ nf
(\n -> runStateGen_ (mkStdGen 1337) $ \g -> replicateM_ n $ do !_ <- uniformDoublePositive01M g
return ()
) sz
]
, bgroup "pure"
[ let !stdGen = mkStdGen 1337
in bench "uniformFloat01M" $ nf
(genMany (runState $ uniformFloat01M (StateGenM :: StateGenM StdGen)) stdGen)
sz
, let !stdGen = mkStdGen 1337
in bench "uniformFloatPositive01M" $ nf
(genMany (runState $ uniformFloatPositive01M (StateGenM :: StateGenM StdGen)) stdGen)
sz
, let !stdGen = mkStdGen 1337
in bench "uniformDouble01M" $ nf
(genMany (runState $ uniformDouble01M (StateGenM :: StateGenM StdGen)) stdGen)
sz
, let !stdGen = mkStdGen 1337
in bench "uniformDoublePositive01M" $ nf
(genMany (runState $ uniformDoublePositive01M (StateGenM :: StateGenM StdGen)) stdGen)
sz
]
]
, bgroup "ShortByteString"
[ env (pure genLengths) $ \ ~(ns, gen) ->
bench "genShortByteString" $
nfIO $ runStateGenT_ gen $ \g -> mapM (`uniformShortByteString` g) ns
]
]
]
]

pureRandomBench :: forall a. (Typeable a, Random a) => Proxy a -> Int -> Benchmark
pureRandomBench px =
let !stdGen = mkStdGen 1337
in pureBench px (genMany (random :: StdGen -> (a, StdGen)) stdGen)

pureUniformBench :: forall a. (Typeable a, Uniform a) => Proxy a -> Int -> Benchmark
pureUniformBench px =
let !stdGen = mkStdGen 1337
in pureBench px (genMany (uniform :: StdGen -> (a, StdGen)) stdGen)

pureUniformRFullBench ::
forall a. (Typeable a, UniformRange a, Bounded a)
=> Proxy a
-> Int
-> Benchmark
pureUniformRFullBench px =
let range = (minBound :: a, maxBound :: a)
in pureUniformRBench px range

pureUniformRExcludeMaxBench ::
forall a. (Typeable a, UniformRange a, Bounded a, Enum a)
=> Proxy a
-> Int
-> Benchmark
pureUniformRExcludeMaxBench px =
let range = (minBound :: a, pred (maxBound :: a))
in pureUniformRBench px range

pureUniformRIncludeHalfBench ::
forall a. (Typeable a, UniformRange a, Bounded a, Integral a)
=> Proxy a
-> Int
-> Benchmark
pureUniformRIncludeHalfBench px =
let range = ((minBound :: a) + 1, ((maxBound :: a) `div` 2) + 1)
in pureUniformRBench px range

pureUniformRIncludeHalfEnumBench ::
forall a. (Typeable a, UniformRange a, Bounded a, Enum a)
=> Proxy a
-> Int
-> Benchmark
pureUniformRIncludeHalfEnumBench px =
let range = (succ (minBound :: a), toEnum ((fromEnum (maxBound :: a) `div` 2) + 1))
in pureUniformRBench px range

pureUniformRBench ::
forall a. (Typeable a, UniformRange a)
=> Proxy a
-> (a, a)
-> Int
-> Benchmark
pureUniformRBench px range@(!_, !_) =
let !stdGen = mkStdGen 1337
in pureBench px (genMany (uniformR range) stdGen)

pureBench :: forall a. (Typeable a) => Proxy a -> (Int -> ()) -> Int -> Benchmark
pureBench px f sz = bench (showsTypeRep (typeRep px) "") $ nf f sz

genMany :: (g -> (a, g)) -> g -> Int -> ()
genMany f g0 n = go g0 0
where
go g i
| i < n =
case f g of
(x, g') -> x `seq` go g' (i + 1)
| otherwise = g `seq` ()
1 change: 0 additions & 1 deletion prologue.txt

This file was deleted.

249 changes: 186 additions & 63 deletions random.cabal
Original file line number Diff line number Diff line change
@@ -1,70 +1,193 @@
name: random
version: 1.1




license: BSD3
license-file: LICENSE
maintainer: core-libraries-committee@haskell.org
bug-reports: https://github.com/haskell/random/issues
synopsis: random number library
category: System
cabal-version: >=1.10
name: random
version: 1.2.0
license: BSD3
license-file: LICENSE
maintainer: core-libraries-committee@haskell.org
bug-reports: https://github.com/haskell/random/issues
synopsis: Pseudo-random number generation
description:
This package provides a basic random number generation
library, including the ability to split random number
generators.

This package provides basic pseudo-random number generation, including the
ability to split random number generators.
.
== "System.Random": pure pseudo-random number interface
.
In pure code, use 'System.Random.uniform' and 'System.Random.uniformR' from
"System.Random" to generate pseudo-random numbers with a pure pseudo-random
number generator like 'System.Random.StdGen'.
.
As an example, here is how you can simulate rolls of a six-sided die using
'System.Random.uniformR':
.
>>> let roll = uniformR (1, 6) :: RandomGen g => g -> (Word, g)
>>> let rolls = unfoldr (Just . roll) :: RandomGen g => g -> [Word]
>>> let pureGen = mkStdGen 42
>>> take 10 (rolls pureGen) :: [Word]
[1,1,3,2,4,5,3,4,6,2]
.
See "System.Random" for more details.
.
== "System.Random.Stateful": monadic pseudo-random number interface
.
In monadic code, use 'System.Random.Stateful.uniformM' and
'System.Random.Stateful.uniformRM' from "System.Random.Stateful" to generate
pseudo-random numbers with a monadic pseudo-random number generator, or
using a monadic adapter.
.
As an example, here is how you can simulate rolls of a six-sided die using
'System.Random.Stateful.uniformRM':
.
>>> let rollM = uniformRM (1, 6) :: StatefulGen g m => g -> m Word
>>> let pureGen = mkStdGen 42
>>> runStateGen_ pureGen (replicateM 10 . rollM) :: [Word]
[1,1,3,2,4,5,3,4,6,2]
.
The monadic adapter 'System.Random.Stateful.runGenState_' is used here to lift
the pure pseudo-random number generator @pureGen@ into the
'System.Random.Stateful.StatefulGen' context.
.
The monadic interface can also be used with existing monadic pseudo-random
number generators. In this example, we use the one provided in the
<https://hackage.haskell.org/package/mwc-random mwc-random> package:
.
>>> import System.Random.MWC as MWC
>>> let rollM = uniformRM (1, 6) :: StatefulGen g m => g -> m Word
>>> monadicGen <- MWC.create
>>> replicateM 10 (rollM monadicGen) :: IO [Word]
[2,3,6,6,4,4,3,1,5,4]
.
See "System.Random.Stateful" for more details.

category: System
build-type: Simple
extra-source-files:
.travis.yml
README.md
CHANGELOG.md
.gitignore
.darcs-boring



build-type: Simple
-- cabal-version 1.8 needed because "the field 'build-depends: random' refers
-- to a library which is defined within the same package"
cabal-version: >= 1.8



Library
exposed-modules:
System.Random
extensions: CPP
GHC-Options: -O2
build-depends: base >= 3 && < 5, time
README.md
CHANGELOG.md
tested-with: GHC == 7.10.2
, GHC == 7.10.3
, GHC == 8.0.2
, GHC == 8.2.2
, GHC == 8.4.3
, GHC == 8.4.4
, GHC == 8.6.3
, GHC == 8.6.4
, GHC == 8.6.5
, GHC == 8.8.1
, GHC == 8.8.2
, GHC == 8.10.1

source-repository head
type: git
location: http://git.haskell.org/packages/random.git
location: https://github.com/haskell/random.git

-- To run the Test-Suite:
-- $ cabal configure --enable-tests
-- $ cabal test --show-details=always --test-options="+RTS -M1M -RTS"

Test-Suite T7936
type: exitcode-stdio-1.0
main-is: T7936.hs
hs-source-dirs: tests
build-depends: base >= 3 && < 5, random
ghc-options: -rtsopts -O2

Test-Suite TestRandomRs
type: exitcode-stdio-1.0
main-is: TestRandomRs.hs
hs-source-dirs: tests
build-depends: base >= 3 && < 5, random
ghc-options: -rtsopts -O2
-- TODO. Why does the following not work?
--test-options: +RTS -M1M -RTS

Test-Suite TestRandomIOs
type: exitcode-stdio-1.0
main-is: TestRandomIOs.hs
hs-source-dirs: tests
build-depends: base >= 3 && < 5, random
ghc-options: -rtsopts -O2
library
exposed-modules:
System.Random
System.Random.Internal
System.Random.Stateful

hs-source-dirs: src
default-language: Haskell2010
ghc-options:
-Wall
if impl(ghc >= 8.0)
ghc-options:
-Wincomplete-record-updates -Wincomplete-uni-patterns

build-depends:
base >=4.8 && <5,
bytestring >=0.10.4 && <0.11,
deepseq >=1.1 && <2,
mtl >=2.2 && <2.3,
splitmix >=0.1 && <0.2
if impl(ghc < 8.0)
build-depends:
transformers

test-suite legacy-test
type: exitcode-stdio-1.0
main-is: Legacy.hs
hs-source-dirs: test-legacy
other-modules:
T7936
TestRandomIOs
TestRandomRs
Random1283
RangeTest

default-language: Haskell2010
ghc-options: -with-rtsopts=-M4M
if impl(ghc >= 8.0)
ghc-options:
-Wno-deprecations
build-depends:
base -any,
containers >=0.5 && <0.7,
random -any

test-suite doctests
type: exitcode-stdio-1.0
main-is: doctests.hs
hs-source-dirs: test
default-language: Haskell2010
build-depends:
base -any,
doctest >=0.15 && <0.18,
mwc-random >=0.13 && <0.15,
primitive >=0.6 && <0.8,
random -any,
unliftio >=0.2 && <0.3,
vector >= 0.10 && <0.14

test-suite spec
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: test
other-modules:
Spec.Range
Spec.Run

default-language: Haskell2010
ghc-options: -Wall
build-depends:
base -any,
bytestring -any,
random -any,
smallcheck >=1.2 && <1.3,
tasty >=1.0 && <1.4,
tasty-smallcheck >=0.8 && <0.9,
tasty-expected-failure >=0.11 && <0.12,
tasty-hunit >=0.10 && <0.11

benchmark legacy-bench
type: exitcode-stdio-1.0
main-is: SimpleRNGBench.hs
hs-source-dirs: bench-legacy
other-modules: BinSearch
default-language: Haskell2010
ghc-options:
-Wall -O2 -threaded -rtsopts -with-rtsopts=-N
if impl(ghc >= 8.0)
ghc-options:
-Wno-deprecations

build-depends:
base -any,
random -any,
rdtsc -any,
split >=0.2 && <0.3,
time >=1.4 && <1.11

benchmark bench
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: bench
default-language: Haskell2010
ghc-options: -Wall -O2
build-depends:
base -any,
gauge >=0.2.3 && <0.3,
mtl,
random -any,
splitmix >=0.1 && <0.2
500 changes: 500 additions & 0 deletions src/System/Random.hs

Large diffs are not rendered by default.

1,111 changes: 1,111 additions & 0 deletions src/System/Random/Internal.hs

Large diffs are not rendered by default.

721 changes: 721 additions & 0 deletions src/System/Random/Stateful.hs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions stack-coveralls.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resolver: lts-14.27
packages:
- .
extra-deps:
- splitmix-0.1@sha256:d50c4d0801a35be7875a040470c09863342514930c82a7d25780a6c2efc4fda9,5249
- rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557
- smallcheck-1.2.0@sha256:8b431572e6a0503223e0e52014d41084c1b01f2aeea3bd499f6f529b3f6dfa89,1482
9 changes: 9 additions & 0 deletions stack-old.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resolver: lts-11.22
packages:
- .
extra-deps:
- splitmix-0.1@sha256:d50c4d0801a35be7875a040470c09863342514930c82a7d25780a6c2efc4fda9,5249
- doctest-0.16.2@sha256:2f96e9bbe9aee11b47453c82c24b3dc76cdbb8a2a7c984dfd60b4906d08adf68,6942
- cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491
- rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557
- smallcheck-1.2.0@sha256:8b431572e6a0503223e0e52014d41084c1b01f2aeea3bd499f6f529b3f6dfa89,1482
30 changes: 30 additions & 0 deletions stack-really-old.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
resolver: lts-3.22
packages:
- .
extra-deps:
- splitmix-0.1@sha256:d50c4d0801a35be7875a040470c09863342514930c82a7d25780a6c2efc4fda9,5249
- doctest-0.17@sha256:684b9736321c255fc8da651a817a14d46afd9aa08a753b344abaa77b343123f2,6988
- cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491
- rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557
- smallcheck-1.2.0@sha256:8b431572e6a0503223e0e52014d41084c1b01f2aeea3bd499f6f529b3f6dfa89,1482
- tasty-1.3.1@sha256:01e35c97f7ee5ccbc28f21debea02a38cd010d53b4c3087f5677c5d06617a507,2520
- tasty-smallcheck-0.8.1@sha256:37728da9451a857c7410353267c995abea92f8f395fcabd9fe7bf732cc2e3e2e,1013
- tasty-hunit-0.10.0.2@sha256:8e8bd5807cec650f5aebc5ada07b57620c863e69145e65249651c1b48d97bd70,1515
- ansi-wl-pprint-0.6.8.2@sha256:a890b713942c1aa0109fb632e9fee581ceb5b0763fd936ae8cae22e5f91a0877,2178
- ansi-terminal-0.9.1@sha256:48f53532d0f365ffa568c8cf0adc84c66f800a7d80d3329e4f04fa75392f4af1,3225
- optparse-applicative-0.15.1.0@sha256:29ff6146aabf54d46c4c8788e8d1eadaea27c94f6d360c690c5f6c93dac4b07e,4810
- unliftio-0.2.11@sha256:ab67a81f31f658c23d096881711ef3a7bda53b819b8149446fca7cc500385859,3064
- unliftio-core-0.2.0.1@sha256:9b3e44ea9aacacbfc35b3b54015af450091916ac3618a41868ebf6546977659a,1082
- wcwidth-0.0.2@sha256:77531eb6683c505c22ab3fa11bbc43d3ce1e7dac21401d4d5a19677d348bb5f3,1998


- semigroups-0.18@sha256:1ac1e991b2f97eab4e4526137b0b5ec6ac3791868e56e89242501cd2cdd7db97,3996
- hashable-1.2.7.0@sha256:03b6836ca9cd3ad0e5a2f3cce989b001dd0e05f306a873db3196037adb30e0a4,5215

- async-2.2.2@sha256:a178c166856da7ff22fe4500337b54812e94fd2059409452187d72e057ede9cd,2934
- call-stack-0.2.0@sha256:5ce796b78d5f964468ec6fe0717b4e7d0430817f37370c47b3e6b38e345b6643,1202
- code-page-0.2@sha256:f701393cb1ff7b3ec7880816abc44387647811be31670e884e02d6a20c4aa508,2356
- fail-4.9.0.0@sha256:35d1ee29364447c1b7d616bb1ee31f162b73e85fea91d7ca6441cf901398f572,1051
- tasty-expected-failure-0.11.1.2@sha256:ff9bad876ec28415e8fb6837362f2086331cf23d7f1468536aaabd91d53f66e4,1826
- gauge-0.2.5@sha256:8d60450bdec985c146d5632d4f5a8d60cec27d71ba6787ed1ee64d945d4c7c33,3923
- basement-0.0.6@sha256:8ef28b7ee2419f410919fb938fcf1e160d84759736baf007eb056a17bc34924a,5578
7 changes: 7 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resolver: lts-15.1
packages:
- .
extra-deps:
- splitmix-0.1@sha256:d50c4d0801a35be7875a040470c09863342514930c82a7d25780a6c2efc4fda9,5249
- doctest-0.17@sha256:684b9736321c255fc8da651a817a14d46afd9aa08a753b344abaa77b343123f2,6988
- smallcheck-1.2.0@sha256:8b431572e6a0503223e0e52014d41084c1b01f2aeea3bd499f6f529b3f6dfa89,1482
15 changes: 15 additions & 0 deletions test-legacy/Legacy.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Main (main) where

import qualified Random1283 as Random1283
import qualified RangeTest as RangeTest
import qualified T7936 as T7936
import qualified TestRandomIOs as TestRandomIOs
import qualified TestRandomRs as TestRandomRs

main :: IO ()
main = do
Random1283.main
RangeTest.main
T7936.main
TestRandomIOs.main
TestRandomRs.main
20 changes: 14 additions & 6 deletions tests/random1283.hs → test-legacy/Random1283.hs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
module Random1283 (main) where

import Control.Concurrent
import Control.Monad hiding (empty)
import Data.Sequence (ViewL(..), empty, fromList, viewl, (<|), (|>), (><))
import Control.Monad
import Data.Sequence (Seq, ViewL(..), empty, fromList, viewl, (<|), (|>), (><))
import System.Random

-- This test

threads, samples :: Int
threads = 4
samples = 5000

main :: IO ()
main = loopTest threads samples

loopTest :: Int -> Int -> IO ()
loopTest t s = do
isClean <- testRace t s
when (not isClean) $ putStrLn "race condition!"
unless isClean $ putStrLn "race condition!"

testRace :: Int -> Int -> IO Bool
testRace t s = do
ref <- liftM (take (t*s) . randoms) getStdGen
iss <- threadRandoms t s
@@ -23,18 +29,20 @@ threadRandoms :: Random a => Int -> Int -> IO [[a]]
threadRandoms t s = do
vs <- sequence $ replicate t $ do
v <- newEmptyMVar
forkIO (sequence (replicate s randomIO) >>= putMVar v)
_ <- forkIO (sequence (replicate s randomIO) >>= putMVar v)
return v
mapM takeMVar vs

isInterleavingOf xs yss = iio xs (viewl $ fromList yss) EmptyL where
isInterleavingOf :: Eq a => [a] -> [[a]] -> Bool
isInterleavingOf xs' yss' = iio xs' (viewl $ fromList yss') EmptyL where
iio (x:xs) ((y:ys) :< yss) zss
| x /= y = iio (x:xs) (viewl yss) (viewl (fromViewL zss |> (y:ys)))
| x == y = iio xs (viewl ((ys <| yss) >< fromViewL zss)) EmptyL
iio xs ([] :< yss) zss = iio xs (viewl yss) zss
iio [] EmptyL EmptyL = True
iio _ _ _ = False

fromViewL (EmptyL) = empty
fromViewL :: ViewL a -> Seq a
fromViewL EmptyL = empty
fromViewL (x :< xs) = x <| xs

73 changes: 38 additions & 35 deletions tests/rangeTest.hs → test-legacy/RangeTest.hs
Original file line number Diff line number Diff line change
@@ -1,60 +1,61 @@
{-# LANGUAGE CPP #-}
module RangeTest (main) where

import Control.Monad
import System.Random
import Data.Int
import Data.Word
import Data.Bits
import Foreign.C.Types

-- Take many measurements and record the max/min/average random values.
approxBounds :: (RandomGen g, Random a, Ord a, Num a) =>
(g -> (a,g)) -> Int -> a -> (a,a) -> g -> ((a,a,a),g)
approxBounds ::
(RandomGen g, Random a, Ord a, Num a) =>
(g -> (a,g)) -> Int -> a -> (a,a) -> g -> ((a,a,a),g)
-- Here we do a little hack to essentiall pass in the type in the last argument:
approxBounds nxt iters unused (explo,exphi) initrng =
if False
approxBounds nxt iters unused (explo,exphi) initrng =
if False
then ((unused,unused,unused),undefined)
-- else loop initrng iters 100 (-100) 0 -- Oops, can't use minBound/maxBound here.
else loop initrng iters exphi explo 0 -- Oops, can't use minBound/maxBound here.
where
loop rng 0 mn mx sum = ((mn,mx,sum),rng)
loop rng n mn mx sum =
case nxt rng of
(x, rng') -> loop rng' (n-1) (min x mn) (max x mx) (x+sum)
else loop initrng iters exphi explo 0
where
loop rng 0 mn mx sum' = ((mn,mx,sum'),rng)
loop rng n mn mx sum' =
case nxt rng of
(x, rng') -> loop rng' (n-1) (min x mn) (max x mx) (x+sum')


-- We check that:
-- (1) all generated numbers are in bounds
-- (2) we get "close" to the bounds
-- The with (2) is that we do enough trials to ensure that we can at
-- least hit the 90% mark.
checkBounds:: (Real a, Show a, Ord a) =>
String -> (Bool, a, a) -> ((a,a) -> StdGen -> ((a, a, t), StdGen)) -> IO ()
checkBounds msg (exclusive,lo,hi) fun =
-- (lo,hi) is [inclusive,exclusive)
do putStr$ msg
-- ++ ", expected range " ++ show (lo,hi)
++ ": "
(mn,mx,sum) <- getStdRandom (fun (lo,hi))
when (mn < lo)$ error$ "broke lower bound: " ++ show mn
when (mx > hi) $ error$ "broke upper bound: " ++ show mx
when (exclusive && mx >= hi)$ error$ "hit upper bound: " ++ show mx
checkBounds ::
(Real a, Show a, Ord a) =>
String -> (Bool, a, a) -> ((a,a) -> StdGen -> ((a, a, t), StdGen)) -> IO ()
checkBounds msg (exclusive,lo,hi) fun = do
-- (lo,hi) is [inclusive,exclusive)
putStr $ msg ++ ": "
(mn,mx,_) <- getStdRandom (fun (lo,hi))
when (mn < lo) $ error $ "broke lower bound: " ++ show mn
when (mx > hi) $ error $ "broke upper bound: " ++ show mx
when (exclusive && mx >= hi)$ error$ "hit upper bound: " ++ show mx

let epsilon = 0.1 * (toRational hi - toRational lo)
let epsilon = 0.1 * (toRational hi - toRational lo)

when (toRational (hi - mx) > epsilon)$ error$ "didn't get close enough to upper bound: "++ show mx
when (toRational (mn - lo) > epsilon)$ error$ "didn't get close enough to lower bound: "++ show mn
putStrLn "Passed"
when (toRational (hi - mx) > epsilon) $ error $ "didn't get close enough to upper bound: "++ show mx
when (toRational (mn - lo) > epsilon) $ error $ "didn't get close enough to lower bound: "++ show mn
putStrLn "Passed"

boundedRange :: (Num a, Bounded a) => (Bool, a, a)
boundedRange = ( False, minBound, maxBound )

trials :: Int
trials = 5000

-- Keep in mind here that on some architectures (e.g. ARM) CChar, CWchar, and CSigAtomic
-- are unsigned

main =
do
main :: IO ()
main =
do
checkBounds "Int" boundedRange (approxBounds random trials (undefined::Int))
checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int))
(approxBounds random trials (undefined::Integer))
@@ -67,8 +68,8 @@ main =
checkBounds "Word16" boundedRange (approxBounds random trials (undefined::Word16))
checkBounds "Word32" boundedRange (approxBounds random trials (undefined::Word32))
checkBounds "Word64" boundedRange (approxBounds random trials (undefined::Word64))
checkBounds "Double" (True,0.0,1.0) (approxBounds random trials (undefined::Double))
checkBounds "Float" (True,0.0,1.0) (approxBounds random trials (undefined::Float))
checkBounds "Double" (False,0.0,1.0) (approxBounds random trials (undefined::Double))
checkBounds "Float" (False,0.0,1.0) (approxBounds random trials (undefined::Float))

checkBounds "CChar" boundedRange (approxBounds random trials (undefined:: CChar))
checkBounds "CSChar" boundedRange (approxBounds random trials (undefined:: CSChar))
@@ -92,7 +93,9 @@ main =

-- Then check all the range-restricted versions:
checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int))
checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer))
checkBounds "Integer R"
(False,-100000000000000000000,100000000000000000000)
(approxBounds (randomR (-100000000000000000000,100000000000000000000)) trials (undefined::Integer))
checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8))
checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8))
checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8))
@@ -106,8 +109,8 @@ main =
checkBounds "Word16 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word16))
checkBounds "Word32 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word32))
checkBounds "Word64 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word64))
checkBounds "Double R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double))
checkBounds "Float R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float))
checkBounds "Double R" (False,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double))
checkBounds "Float R" (False,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float))

checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar))
checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar))
3 changes: 2 additions & 1 deletion tests/T7936.hs → test-legacy/T7936.hs
Original file line number Diff line number Diff line change
@@ -6,9 +6,10 @@
-- $ cabal test T7936 --test-options="+RTS -M1M -RTS"
-- T7936: Heap exhausted;

module Main where
module T7936 where

import System.Random (newStdGen)
import Control.Monad (replicateM_)

main :: IO ()
main = replicateM_ 100000 newStdGen
3 changes: 2 additions & 1 deletion tests/TestRandomIOs.hs → test-legacy/TestRandomIOs.hs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
-- $ cabal test TestRandomIOs --test-options="+RTS -M1M -RTS"
-- TestRandomIOs: Heap exhausted;

module Main where
module TestRandomIOs where

import Control.Monad (replicateM)
import System.Random (randomIO)
@@ -15,6 +15,7 @@ import System.Random (randomIO)
-- the last one.
-- Should use less than 1Mb of heap space, or we are generating a list of
-- unevaluated thunks.
main :: IO ()
main = do
rs <- replicateM 5000 randomIO :: IO [Int]
print $ last rs
5 changes: 3 additions & 2 deletions tests/TestRandomRs.hs → test-legacy/TestRandomRs.hs
Original file line number Diff line number Diff line change
@@ -10,13 +10,14 @@
-- $ cabal test TestRandomRs --test-options="+RTS -M1M -RTS"
-- TestRandomRs: Heap exhausted;

module Main where
module TestRandomRs where

import Control.Monad (liftM, replicateM)
import Control.Monad (liftM)
import System.Random (randomRs, getStdGen)

-- Return the five-thousandth random number:
-- Should run in constant space (< 1Mb heap).
main :: IO ()
main = do
n <- (last . take 5000 . randomRs (0, 1000000)) `liftM` getStdGen
print (n::Integer)
143 changes: 143 additions & 0 deletions test/Spec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where

import Data.ByteString.Short as SBS
import Data.Int
import Data.Typeable
import Data.Word
import Foreign.C.Types
import Numeric.Natural (Natural)
import System.Random
import Test.SmallCheck.Series as SC
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.SmallCheck as SC

import qualified Spec.Range as Range
import qualified Spec.Run as Run

main :: IO ()
main =
defaultMain $
testGroup
"Spec"
[ floatingSpec (Proxy :: Proxy Double)
, floatingSpec (Proxy :: Proxy Float)
, floatingSpec (Proxy :: Proxy CDouble)
, floatingSpec (Proxy :: Proxy CFloat)
, integralSpec (Proxy :: Proxy Word8)
, integralSpec (Proxy :: Proxy Word16)
, integralSpec (Proxy :: Proxy Word32)
, integralSpec (Proxy :: Proxy Word64)
, integralSpec (Proxy :: Proxy Word)
, integralSpec (Proxy :: Proxy Int8)
, integralSpec (Proxy :: Proxy Int16)
, integralSpec (Proxy :: Proxy Int32)
, integralSpec (Proxy :: Proxy Int64)
, integralSpec (Proxy :: Proxy Int)
, integralSpec (Proxy :: Proxy Char)
, integralSpec (Proxy :: Proxy Bool)
#if __GLASGOW_HASKELL >= 802
, integralSpec (Proxy :: Proxy CBool)
#endif
, integralSpec (Proxy :: Proxy CChar)
, integralSpec (Proxy :: Proxy CSChar)
, integralSpec (Proxy :: Proxy CUChar)
, integralSpec (Proxy :: Proxy CShort)
, integralSpec (Proxy :: Proxy CUShort)
, integralSpec (Proxy :: Proxy CInt)
, integralSpec (Proxy :: Proxy CUInt)
, integralSpec (Proxy :: Proxy CLong)
, integralSpec (Proxy :: Proxy CULong)
, integralSpec (Proxy :: Proxy CPtrdiff)
, integralSpec (Proxy :: Proxy CSize)
, integralSpec (Proxy :: Proxy CWchar)
, integralSpec (Proxy :: Proxy CSigAtomic)
, integralSpec (Proxy :: Proxy CLLong)
, integralSpec (Proxy :: Proxy CULLong)
, integralSpec (Proxy :: Proxy CIntPtr)
, integralSpec (Proxy :: Proxy CUIntPtr)
, integralSpec (Proxy :: Proxy CIntMax)
, integralSpec (Proxy :: Proxy CUIntMax)
, integralSpec (Proxy :: Proxy Integer)
, integralSpec (Proxy :: Proxy Natural)
, runSpec
, floatTests
, byteStringSpec
, SC.testProperty "uniformRangeWithinExcludedF" $ seeded Range.uniformRangeWithinExcludedF
, SC.testProperty "uniformRangeWithinExcludedD" $ seeded Range.uniformRangeWithinExcludedD
]

floatTests :: TestTree
floatTests = testGroup "(Float)"
[ -- Check that https://github.com/haskell/random/issues/53 does not regress

testCase "Subnormal generation not above upper bound" $
[] @?= filter (>4.0e-45) (take 100000 $ randomRs (0, 4.0e-45::Float) $ mkStdGen 0)

, testCase "Subnormal generation includes upper bound" $
1.0e-45 `elem` take 100 (randomRs (0, 1.0e-45::Float) $ mkStdGen 0) @?
"Does not contain 1.0e-45"
]

showsType :: forall t . Typeable t => Proxy t -> ShowS
showsType px = showsTypeRep (typeRep px)

byteStringSpec :: TestTree
byteStringSpec =
testGroup
"ByteString"
[ SC.testProperty "genShortByteString" $ \(seed, n8) ->
let n = fromIntegral (n8 :: Word8) -- no need to generate huge collection of bytes
in SBS.length (fst (seeded (genShortByteString n) seed)) == n
, SC.testProperty "genByteString" $ \(seed, n8) ->
let n = fromIntegral (n8 :: Word8)
in SBS.toShort (fst (seeded (genByteString n) seed)) ==
fst (seeded (genShortByteString n) seed)
]


rangeSpec ::
forall a.
(SC.Serial IO a, Typeable a, Ord a, UniformRange a, Show a)
=> Proxy a -> TestTree
rangeSpec px =
testGroup ("Range (" ++ showsType px ")")
[ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithin px
]

integralSpec ::
forall a.
(SC.Serial IO a, Typeable a, Ord a, UniformRange a, Show a)
=> Proxy a -> TestTree
integralSpec px =
testGroup ("(" ++ showsType px ")")
[ SC.testProperty "symmetric" $ seeded $ Range.symmetric px
, SC.testProperty "bounded" $ seeded $ Range.bounded px
, SC.testProperty "singleton" $ seeded $ Range.singleton px
, rangeSpec px
-- TODO: Add more tests
]

floatingSpec ::
forall a.
(SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a)
=> Proxy a -> TestTree
floatingSpec px =
testGroup ("(" ++ showsType px ")")
[ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithin px
-- TODO: Add more tests
]

runSpec :: TestTree
runSpec = testGroup "runGenState_ and runPrimGenIO_"
[ SC.testProperty "equal outputs" $ seeded $ \g -> monadic $ Run.runsEqual g ]

-- | Create a StdGen instance from an Int and pass it to the given function.
seeded :: (StdGen -> a) -> Int -> a
seeded f = f . mkStdGen
42 changes: 42 additions & 0 deletions test/Spec/Range.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Spec.Range
( symmetric
, bounded
, singleton
, uniformRangeWithin
, uniformRangeWithinExcludedF
, uniformRangeWithinExcludedD
) where

import System.Random.Internal
import System.Random.Stateful
import Data.Proxy

symmetric :: (RandomGen g, UniformRange a, Eq a) => Proxy a -> g -> (a, a) -> Bool
symmetric _ g (l, r) = fst (uniformR (l, r) g) == fst (uniformR (r, l) g)

bounded :: (RandomGen g, UniformRange a, Ord a) => Proxy a -> g -> (a, a) -> Bool
bounded _ g (l, r) = bottom <= result && result <= top
where
bottom = min l r
top = max l r
result = fst (uniformR (l, r) g)

singleton :: (RandomGen g, UniformRange a, Eq a) => Proxy a -> g -> a -> Bool
singleton _ g x = result == x
where
result = fst (uniformR (x, x) g)

uniformRangeWithin :: (RandomGen g, UniformRange a, Ord a) => Proxy a -> g -> (a, a) -> Bool
uniformRangeWithin _ gen (l, r) =
runStateGen_ gen $ \g ->
(\result -> min l r <= result && result <= max l r) <$> uniformRM (l, r) g

uniformRangeWithinExcludedF :: RandomGen g => g -> Bool
uniformRangeWithinExcludedF gen =
runStateGen_ gen $ \g ->
(\result -> 0 < result && result <= 1) <$> uniformFloatPositive01M g

uniformRangeWithinExcludedD :: RandomGen g => g -> Bool
uniformRangeWithinExcludedD gen =
runStateGen_ gen $ \g ->
(\result -> 0 < result && result <= 1) <$> uniformDoublePositive01M g
14 changes: 14 additions & 0 deletions test/Spec/Run.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Spec.Run (runsEqual) where

import Data.Word (Word64)
import System.Random.Stateful

runsEqual :: RandomGen g => g -> IO Bool
runsEqual g = do
let pureResult = runStateGen_ g uniformM :: Word64
stResult = runSTGen_ g uniformM :: Word64
ioGenM <- newIOGenM g
ioResult <- uniformM ioGenM
atomicGenM <- newAtomicGenM g
atomicResult <- uniformM atomicGenM
return $ all (pureResult ==) [stResult, ioResult, atomicResult]
17 changes: 17 additions & 0 deletions test/doctests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{-# LANGUAGE CPP #-}
module Main where

#if __GLASGOW_HASKELL__ >= 802

import Test.DocTest (doctest)

main :: IO ()
main = doctest ["src"]

#else

-- TODO: fix doctest support
main :: IO ()
main = putStrLn "\nDoctests are not supported for older ghc version\n"

#endif
14 changes: 0 additions & 14 deletions tests/Makefile

This file was deleted.

10 changes: 0 additions & 10 deletions tests/all.T

This file was deleted.

67 changes: 0 additions & 67 deletions tests/rangeTest.stdout

This file was deleted.

55 changes: 0 additions & 55 deletions tests/slowness.hs

This file was deleted.

0 comments on commit 64fa9bb

Please sign in to comment.