Skip to content

Commit

Permalink
parent cfdfe6f
Browse files Browse the repository at this point in the history
author Alexey Kuleshevich <[email protected]> 1581472095 +0300
committer Leonhard Markert <[email protected]> 1590493894 +0200

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
===========

MonadRandom
-----------

This patch adds a class 'MonadRandom':

    -- | 'MonadRandom' is an interface to monadic pseudo-random number generators.
    class Monad m => MonadRandom g s m | g m -> s where
      {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-}

      type Frozen g = (f :: Type) | f -> g
      freezeGen :: g s -> m (Frozen g)
      thawGen :: Frozen g -> m (g s)

      uniformWord32 :: g s -> m Word32 -- default implementation in terms of uniformWord64
      uniformWord64 :: g s -> 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

Conceptually, in 'MonadRandom g s m', 'g s' is the type of the
generator, 's' is the state type, and 'm' the underlying monad. Via the
functional dependency 'g m -> s', the state type is determined by the
generator and monad.

'Frozen' is the type of the generator's state "at rest". It is defined
as an injective type family via 'f -> g', so there is no ambiguity as to
which 'g' any 'Frozen g' belongs to.

This definition is 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 'MonadRandom Gen' instance.

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

'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]>
  • Loading branch information
3 people committed May 26, 2020
1 parent 326cc7e commit a01df83
Show file tree
Hide file tree
Showing 37 changed files with 3,533 additions and 1,574 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
*.lock
.stack-work/
cabal.project.local
173 changes: 168 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,168 @@
language: haskell
ghc:
- 7.4
- 7.6
- 7.8
###############################################################################
# To see the local changes made to this file, run:
#
# $ curl -sS https://raw.githubusercontent.com/commercialhaskell/stack/0cbcce0b1b89e5431121b745afdd1dfbd3ba8a43/doc/travis-complex.yml | diff -u - .travis.yml
###############################################################################

# This is the complex Travis configuration, which is intended for use
# on open source libraries which need compatibility across multiple GHC
# versions, must work with cabal-install, and should be
# cross-platform. For more information and other options, see:
#
# https://docs.haskellstack.org/en/stable/travis_ci/
#
# Copy these contents into the root directory of your Github project in a file
# named .travis.yml

# Run jobs on Linux unless "os" is specified explicitly.
os: linux

# Do not choose a language; we provide our own build tools.
language: generic

# Caching so the next build will be fast too.
cache:
directories:
- $HOME/.ghc
- $HOME/.cabal
- $HOME/.stack
- $TRAVIS_BUILD_DIR/.stack-work

# The different configurations we want to test. We have BUILD=cabal which uses
# cabal-install, and BUILD=stack which uses Stack. More documentation on each
# of those below.
#
# We set the compiler values here to tell Travis to use a different
# cache file per set of arguments.
#
# If you need to have different apt packages for each combination in the
# job matrix, you can use a line such as:
# addons: {apt: {packages: [libfcgi-dev,libgmp-dev]}}
jobs:
include:
# We grab the appropriate GHC and cabal-install versions from hvr's PPA. See:
# https://github.com/hvr/multi-ghc-travis
- env: BUILD=stack ARGS="--resolver lts-11.22 --stack-yaml stack-old.yaml"
compiler: ": #stack 8.2.2"
addons: {apt: {packages: [libgmp-dev]}}

- env: BUILD=stack ARGS="--resolver lts-12.26 --stack-yaml stack-old.yaml"
compiler: ": #stack 8.4.4"
addons: {apt: {packages: [libgmp-dev]}}

- env: BUILD=stack ARGS="--resolver lts-14.27" COVERALLS_STACK_YAML="stack-coveralls.yaml"
compiler: ": #stack 8.6.5"
addons: {apt: {packages: [libgmp-dev]}}

- env: BUILD=stack ARGS="--resolver lts-15"
compiler: ": #stack 8.8.3"
addons: {apt: {packages: [libgmp-dev]}}

- env: BUILD=stack ARGS="--resolver nightly"
compiler: ": #stack"
addons: {apt: {packages: [libgmp-dev]}}

allow_failures:
- env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7
- env: BUILD=stack ARGS="--resolver nightly"

before_install:
# Using compiler above sets CC to an invalid value, so unset it
- unset CC

# We want to always allow newer versions of packages when building on GHC HEAD
- CABALARGS=""
- if [ "x$GHCVER" = "xhead" ]; then CABALARGS=--allow-newer; fi

# Download and unpack the stack executable
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH
- mkdir -p ~/.local/bin
- |
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
# Use the more reliable S3 mirror of Hackage
mkdir -p $HOME/.cabal
echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config
echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config
install:
- echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
- if [ -f configure.ac ]; then autoreconf -i; fi
- |
set -ex
case "$BUILD" in
stack)
# Add in extra-deps for older snapshots, as necessary
#
# This is disabled by default, as relying on the solver like this can
# make builds unreliable. Instead, if you have this situation, it's
# recommended that you maintain multiple stack-lts-X.yaml files.
#stack --no-terminal --install-ghc $ARGS test --bench --dry-run || ( \
# stack --no-terminal $ARGS build cabal-install && \
# stack --no-terminal $ARGS solver --update-config)
# Build the dependencies
# stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies
;;
cabal)
cabal --version
travis_retry cabal update
# Get the list of packages from the stack.yaml file. Note that
# this will also implicitly run hpack as necessary to generate
# the .cabal files needed by cabal-install.
PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@')
cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES
;;
esac
set +ex
script:
- |
set -ex
case "$BUILD" in
stack)
BUILD_ARGS="--bench --no-run-benchmarks --haddock --no-haddock-deps"
if [ -n "${COVERALLS_STACK_YAML}" ] && [ -n "${COVERALLS_REPO_TOKEN}" ]; then
stack $ARGS --stack-yaml="$COVERALLS_STACK_YAML" test --coverage $BUILD_ARGS
stack $ARGS --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 --repo-token=$COVERALLS_REPO_TOKEN combined custom
else
stack --no-terminal $ARGS test $BUILD_ARGS
fi
;;
cabal)
cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES
ORIGDIR=$(pwd)
for dir in $PACKAGES
do
cd $dir
cabal check || [ "$CABALVER" == "1.16" ]
cabal sdist
PKGVER=$(cabal info . | awk '{print $2;exit}')
SRC_TGZ=$PKGVER.tar.gz
cd dist
tar zxfv "$SRC_TGZ"
cd "$PKGVER"
cabal configure --enable-tests --ghc-options -O0
cabal build
if [ "$CABALVER" = "1.16" ] || [ "$CABALVER" = "1.18" ]; then
cabal test
else
cabal test --show-details=streaming --log=/dev/stdout
fi
cd $ORIGDIR
done
;;
esac
set +ex
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.

Loading

0 comments on commit a01df83

Please sign in to comment.