mgold/elm-random-pcg/3.0.0

NOTE: this package is not compatible with Elm 0.19.1

Random.Pcg

Generate psuedo-random numbers and values, by constructing generators for them. There are a bunch of basic generators like bool and int that you can build up into fancier generators with functions like list and map.

You run a Generator by calling the step function, which also takes a random Seed, and passes back a new seed. You should never use the same seed twice because you will get the same result! If you need random values over time, you should store the most recent seed in your model.

This is an implementation of PCG by M. E. O'Neil, and is not cryptographically secure.

Getting Started

initialSeed : Int -> Seed

Initialize the state of the random number generator. The input should be a randomly chosen 32-bit integer. You can generate and copy random integers to create a reproducible psuedo-random generator.

$ node
> Math.floor(Math.random()*0xFFFFFFFF)
227852860


-- Elm
seed0 : Seed
seed0 = initialSeed 227852860

Alternatively, you can generate the random integers on page load and pass them through a port. The program will be different every time.

-- Elm
port randomSeed : Int


seed0 : Seed
seed0 = initialSeed randomSeed


-- JS
Elm.ModuleName.fullscreen(
  { randomSeed: Math.floor(Math.random()*0xFFFFFFFF) })

Either way, you should initialize a random seed only once. After that, whenever you use a seed, you'll get another one back.

step : Generator a -> Seed -> ( a, Seed )

Generate a random value as specified by a given Generator, using a Seed and returning a new one.

In the following example, we are trying to generate numbers between 0 and 100 with the int 0 100 generator. Each time we call generate we need to provide a seed. This will produce a random number and a new seed to use if we want to run other generators later.

(x, seed1) = step (int 0 100) seed0
(y, seed2) = step (int 0 100) seed1
(z, seed3) = step (int 0 100) seed2
[x, y, z] -- [85, 0, 38]

Notice that we use different seeds on each line. This is important! If you reuse the same seed, you get the same results.

(x, _) = step (int 0 100) seed0
(y, _) = step (int 0 100) seed0
(z, _) = step (int 0 100) seed0
[x,y,z] -- [85, 85, 85]

As you can see, threading seeds through many calls to step is tedious and error-prone. That's why this library includes many functions to build more complicated generators, allowing you to call step only a small number of times.

Our example is best written as:

(xs, newSeed) = step (list 3 <| int 0 100) seed0
xs -- [85, 0, 38]
generate : (a -> msg) -> Generator a -> Cmd msg

Create a Command that will generate random values according to the supplied Generator.

Think of this function as an alternative to step, since they both provide a way to actually get the random values that you want. This function frees you from worrying about seeds entirely, but as a tradeoff, you get your random values asynchronously, in their own Message. Additionally, due to constraints on third-party packages, it's possible that multiple commands sent at the same moment will return the same values.

You can also think of this function as an alternative to independentSeed, since they both allow you to use randomness in deeply nested components. In the case of this function, it's through sending Commands up the chain that you have to set up anyway.

Basic Generators

type Generator a

A Generator is like a recipe for generating certain random values. So a Generator Int describes how to generate integers and a Generator String describes how to generate strings.

bool : Generator Bool

Create a generator that produces boolean values with equal probability. This example simulates flipping three coins and checking if they're all heads.

threeHeads : Generator Bool
threeHeads =
  map3 (\a b c -> a && b && c) bool bool bool
int : Int -> Int -> Generator Int

Generate 32-bit integers in a given range, inclusive.

int 0 10   -- an integer between zero and ten
int -5 5   -- an integer between -5 and 5


int minInt maxInt  -- an integer in the widest range feasible

This function can produce values outside of the range [minInt, maxInt] but sufficient randomness is not guaranteed.

Performance note: This function will be ~1.5x faster if the range (i.e. max - min + 1) is a power of two. The effect will only be noticable if you are generating tens of thousands of random integers.

float : Float -> Float -> Generator Float

Generate floats in a given range. The following example is a generator that produces numbers between 0 and 1.

probability : Generator Float
probability =
  float 0 1
oneIn : Int -> Generator Bool

Produce True one-in-n times on average.

Do not pass a value less then one to this function.

flippedHeads = oneIn 2
rolled6 = oneIn 6
sample : List a -> Generator (Maybe a)

Given a list, choose an element uniformly at random. Nothing is only produced if the list is empty.

type Direction = North | South | East | West


direction : Generator Direction
direction =
  sample [North, South, East, West]
    |> map (Maybe.withDefault North)

Combining Generators

pair : Generator a -> Generator b -> Generator ( a, b )

Create a pair of random values. A common use of this might be to generate a point in a certain 2D space. Imagine we have a collage that is 400 pixels wide and 200 pixels tall.

randomPoint : Generator (Int,Int)
randomPoint =
    pair (int -200 200) (int -100 100)
list : Int -> Generator a -> Generator (List a)

Create a list of random values of a given length.

floatList : Generator (List Float)
floatList =
    list 10 (float 0 1)


intList : Generator (List Int)
intList =
    list 5 (int 0 100)


intPairs : Generator (List (Int, Int))
intPairs =
    list 10 <| pair (int 0 100) (int 0 100)
maybe : Generator Bool -> Generator a -> Generator (Maybe a)

Produce Just a value on True, and Nothing on False.

You can use bool or oneIn n for the first argument.

choice : a -> a -> Generator a

Choose between two values with equal probability.

type Flip = Heads | Tails


coinFlip : Generator Flip
coinFlip =
  choice Heads Tails
choices : List (Generator a) -> Generator a

Create a generator that chooses a generator from a list of generators with equal probability.

Warning: Do not pass an empty list or your program will crash! In practice this is usually not a problem since you pass a list literal.

frequency : List ( Float, Generator a ) -> Generator a

Create a generator that chooses a generator from a list of generators based on the provided weight. The likelihood of a given generator being chosen is its weight divided by the total weight (which doesn't have to equal 1).

Warning: Do not pass an empty list or your program will crash! In practice this is usually not a problem since you pass a list literal.

Custom Generators

constant : a -> Generator a

Create a generator that always produces the value provided. This is useful when creating complicated chained generators and you need to handle a simple case. It's also useful for the base case of recursive generators.

map : (a -> b) -> Generator a -> Generator b

Transform the values produced by a generator using a stateless function as a callback.

These examples show how to generate letters based on a basic integer generator.

lowercaseLetter : Generator Char
lowercaseLetter =
  map (\n -> Char.fromCode (n + 97)) (int 0 25)


uppercaseLetter : Generator Char
uppercaseLetter =
  map (\n -> Char.fromCode (n + 65)) (int 0 25)
map2 : (a -> b -> c) -> Generator a -> Generator b -> Generator c

Combine two generators. This is useful when you have a function with two arguments that both need to be given random inputs.

pointInCircle : Float -> Generator (Float, Float)
pointInCircle radius =
  let
    r = float 0 radius
    theta = map degrees (float 0 360)
  in
    map2 (curry fromPolar) r theta
(a -> b -> c -> d)
-> Generator a
-> Generator b
-> Generator c
-> Generator d

Combine three generators. This could be used to produce random colors.

rgb : Generator Color.Color
rgb =
  map3 Color.rgb (int 0 255) (int 0 255) (int 0 255)


hsl : Generator Color.Color
hsl =
  map3 Color.hsl (map degrees (float 0 360)) (float 0 1) (float 0 1)
(a -> b -> c -> d -> e)
-> Generator a
-> Generator b
-> Generator c
-> Generator d
-> Generator e

Combine four generators. This could be used to produce random transparent colors.

rgba : Generator Color.Color
rgba =
  map4 Color.rgba (int 0 255) (int 0 255) (int 0 255) (float 0 1)
(a -> b -> c -> d -> e -> f)
-> Generator a
-> Generator b
-> Generator c
-> Generator d
-> Generator e
-> Generator f

Combine five generators.

andMap : Generator (a -> b) -> Generator a -> Generator b

Map over any number of generators.

randomPerson : Generator Person
randomPerson =
  person `map` genFirstName
      `andMap` genLastName
      `andMap` genBirthday
      `andMap` genPhoneNumber
      `andMap` genAddress
      `andMap` genEmail
andThen : Generator a -> (a -> Generator b) -> Generator b

Chain random operations by providing a callback that accepts a randomly-generated value. The random value can be used to drive more randomness.

The argument order matches andThens from core, but requires the use of flip to match map or work with |> chains.

This example shows how we can use andThen to generate a list of random values and random length. Then we use map to apply a stateless function to that list. Assume we already have genName : Generator String defined.

authors : Generator String
authors =
  int 1 5 -- number of authors
  |> (flip andThen) (\i -> list i genName)
  |> map (\ns ->
    case ns of
      [n] ->
        "Author: " ++ n
      n::ns ->
        "Authors: " ++ String.join ", " ns ++ " and " ++ n
      [] ->
        "This can't happen"
    )

If you find yourself calling constant in every branch of the callback, you can probably use map instead.

filter : (a -> Bool) -> Generator a -> Generator a

Filter a generator so that all generated values satisfy the given predicate.

evens : Generator Int
evens =
  filter (\i -> i % 2 == 0) (int minInt maxInt)

Warning: If the predicate is unsatisfiable, the generator will not terminate, your application will crash with a stack overflow, and you will be sad. You should also avoid predicates that are merely very difficult to satisfy.

badCrashingGenerator =
  filter (\_ -> False) anotherGenerator


likelyCrashingGenerator =
  filter (\i -> i % 2000 == 0) (int minInt maxInt)

Working With Seeds

type Seed

A Seed is the source of randomness in the whole system. It hides the current state of the random number generator.

Generators, not seeds, are the primary data structure for generating random values. Generators are much easier to chain and combine than functions that take and return seeds. Creating and managing seeds should happen "high up" in your program.

independentSeed : Generator Seed

A generator that produces a seed that is independent of any other seed in the program. These seeds will generate their own unqiue sequences of random values. They are useful when you need an unknown amount of randomness later but can request only a fixed amount of randomness now.

Let's say you write a component that uses some randomness to initialize itself and then never needs randomness again. You can easily write a Generator Component by mapping over the generators it needs. But if component requires randomness after initialization, it should keep its own independent seed, which it can get by mapping over this generator.

type alias Component = { seed : Seed }


genComponent : Generator Component
genComponent = map Component independentSeed

If you have a lot of components, you can initialize them like so:

genComponents : List (Seed -> a) -> Generator (List a)
genComponents constructors =
    list (List.length constructors) independentSeed
        |> map (List.map2 (<|) constructors)

The independent seeds are extremely likely to be distinct for all practical purposes. However, it is not proven that there are no pathological cases.

fastForward : Int -> Seed -> Seed

Fast forward a seed the given number of steps, which may be negative (the seed will be "rewound"). This allows a single seed to serve as a random-access lookup table of random numbers. (To be sure no one else uses the seed, use step independentSeed to split off your own.)

diceRollTable : Int -> Int
diceRollTable i =
  fastForward i mySeed |> step (int 1 6) |> fst
toJson : Seed -> Value

Serialize a seed as a JSON value to be sent out a port, stored in local storage, and so on. The seed can be recovered using fromJson.

Do not inspect or change the resulting JSON value.

fromJson : Decoder Seed

A JSON decoder that can recover seeds encoded using toJson. Alternatively, pass an integer to create a seed using initialSeed.

Json.Decode.decodeValue fromJson (toJson mySeed) == Ok mySeed

Constants

minInt : Int

The minimum value for randomly generated 32-bit ints.

maxInt : Int

The maximum value for randomly generated 32-bit ints.