This library helps you request pseudo-random values.
It is an implementation of Permuted Congruential Generators by M. E. O'Neil. It is not cryptographically secure.
request : Spec a -> Tepa.Promise m a
Send a request for random values. Say you want to request random points:
import Tepa exposing (Promise)
import Tepa.Random as Random
pointX : Random.Spec Int
pointX =
Random.int -100 100
pointY : Random.Spec Int
pointY =
Random.int -100 100
newPoint : Promise m ( Int, Int )
newPoint =
Tepa.succeed Tuple.pair
|> Tepa.sync (Random.request pointX)
|> Tepa.sync (Random.request pointY)
Each time you run the newPoint
promise, it will resolve to a new 2D point like
(57, 18)
or (-82, 6)
.
Internal.Core.RandomSpec a
A Spec
is a specifcation for requesting random values. For example,
here is a specification for requesting numbers between 1 and 10 inclusive:
import Tepa.Random as Random
oneToTen : Random.Spec Int
oneToTen =
Random.int 1 10
Notice that we are not actually requesting any numbers yet!
We are just constructing the request body that describes what kind of values we want.
To actually get random values, you create a promise with the request
function:
import Tepa exposing (Promise)
import Tepa.Random as Random
newNumber : Promise m Int
newNumber =
Random.request oneToTen
Each time you run this promise, it checks the oneToTen
specification and produces
random integers between one and ten.
When testing scenarios, you specify the target request for your response with the Spec
:
import Tepa.Scenario as Scenario exposing (Scenario)
respondToRandomInt : Scenario flags m e
respondToRandomInt =
Scenario.randomResponse
mySession
(Scenario.textContent "Respond `1` to the first unresolved `oneToTen` request.")
{ layer = myLayer
, spec = oneToTen
, value = 1
}
-- If there is no unresolved `oneToTen` request at the time, the test fails.
int : String -> Basics.Int -> Basics.Int -> Spec Basics.Int
Request 32-bit integers in a given range.
import Tepa exposing (Promise)
import Tepa.Random as Random
singleDigit : Spec Int
singleDigit =
Random.int "singleDigit" 0 9
closeToZero : Spec Int
closeToZero =
Random.int "closeToZero" -5 5
anyInt : Spec Int
anyInt =
Random.int "anyInt" Random.minInt Random.maxInt
The string argument is the ID of the Spec
, and is used to check the equivalence
of the two Specs
in scenario testing. Be sure to specify unique IDs within the
same layer.
This promise can produce values outside of the range [minInt
,
maxInt
] but sufficient randomness is not guaranteed.
This is the TEPA version of int.
float : String -> Basics.Float -> Basics.Float -> Spec Basics.Float
import Tepa.Random as Random
probability : Random.Spec Float
probability =
Random.float "probability" 0 1
The probability
specification specifies values between zero and one with
a uniform distribution. Say it requests a value p
. We can then check if
p < 0.4
if we want something to happen 40% of the time.
The string argument is the ID of the Spec
, and is used to check the equivalence
of the two Specs
in scenario testing. Be sure to specify unique IDs within the
same layer.
This is the TEPA version of float.
uniform : String -> a -> List a -> Spec a
Request values with equal probability. Say we want a random suit for some cards:
import Tepa.Random as Spec
type Suit
= Diamond
| Club
| Heart
| Spade
suit : Random.Spec Suit
suit =
Random.uniform "suit" Diamond [ Club, Heart, Spade ]
That requests all Suit
values with equal probability, 25% each.
The string argument is the ID of the Spec
, and is used to check the equivalence
of the two Specs
in scenario testing. Be sure to specify unique IDs within the
same layer.
Note: Why not have uniform : String -> List a -> Spec a
as the API? It looks
a little prettier in code, but it leads to an awkward question. What do you do
with uniform identifier []
? How can it request an Int
or Float
? The current API
guarantees that we always have at least one value, so we never run into that
question!
weighted : String -> ( Basics.Float, a ) -> List ( Basics.Float, a ) -> Spec a
Request values with a weighted probability. Say we want to simulate a loaded die that lands on ⚄ and ⚅ more often than the other faces:
import Tepa.Random as Random
type Face
= One
| Two
| Three
| Four
| Five
| Six
roll : Random.Sepc Face
roll =
Random.weighted "roll"
( 10, One )
[ ( 10, Two )
, ( 10, Three )
, ( 10, Four )
, ( 20, Five )
, ( 40, Six )
]
So there is a 40% chance of getting Six
, a 20% chance of getting Five
, and
then a 10% chance for each of the remaining faces.
The string argument is the ID of the Spec
, and is used to check the equivalence
of the two Specs
in scenario testing. Be sure to specify unique IDs within the
same layer.
Note: I made the weights add up to 100, but that is not necessary. I always
add up your weights into a total
, and from there, the probability of each case
is weight / total
. Negative weights do not really make sense, so I just flip
them to be positive.
maxInt : Basics.Int
Alias for Random.maxInt
.
The underlying algorithm works well in a specific range of integers. It can request values outside of that range, but they are “not as random”.
The maxInt
that works well is 2147483647
.
minInt : Basics.Int
Alias for Random.minInt
.
The underlying algorithm works well in a specific range of integers. It can request values outside of that range, but they are “not as random”.
The minInt
that works well is -2147483648
.