Random
E.random() resolves the implicit E.Random service, so it never enters R. Its methods keep the names of the random standard library and each returns an effect: random(), uniform(a, b), randint(a, b), choice(seq) and shuffle(seq), where shuffle returns a new list and leaves its input untouched. The default is E.Random.Live, which draws from the process-global generator behind the random module.
@E.
def () -> E.[]:
= yield from E.()
return (yield from .(1, 6))
E.(()) # 1..6, no setup neededIn tests, provide E.Random.Test(seed): every draw is a function of the seed, and one instance advances as a single sequence, so the same seed always reproduces the same run. A test that requests the test_random fixture gets a Test seeded with 0 provided to its effect:
@E.
def (: E.Random.) -> E.[None]:
first = yield from ()
= yield from ()
assert (, ) == (4, 4)Provide generators with .provide(E.Random.Protocol)(...), as with the Clock. This repo's ruff config bans the random module's drawing functions (random.random, random.randint, random.choice, ...) outside the service module, so all code goes through E.random(); building a random.Random(seed) to compute expected values in a test is still allowed.
More examples: test_random.py.