Ecosystem code

An immutable key does not subtype Random.AbstractRNG. If mutable code accepted a key, repeated calls could replay values without an error.

Adapt at the library boundary

Wrap a key or cursor when a library accepts Random.AbstractRNG.

using ImmutableRNGs, Random

function library_estimate(rng::Random.AbstractRNG, n)
    noise = randn(rng, n)
    weights = rand(rng, n)
    return sum(noise .* weights) / n
end

key = Philox4x32(7)
value, cursor = mutably(key) do rng
    library_estimate(rng, 128)
end

(value, cursor_position(cursor) > 0)
(0.005665565167381887, true)

mutably limits wrapper ownership to the call and returns the next cursor. Use it when the library controls its primitive draw count.

Continue from the returned cursor

Resume from the cursor rather than from the original key.

next_value, next_cursor = mutably(cursor) do rng
    library_estimate(rng, 128)
end

replayed_value, replayed_cursor = mutably(cursor) do rng
    library_estimate(rng, 128)
end

(next_value == replayed_value, next_cursor == replayed_cursor)
(true, true)

A fresh MutableRNG(key) restarts the sequence. A fresh MutableRNG(cursor) resumes it.

Separate concurrent chains first

Derive all chain keys before creating wrappers. Do not share a wrapper between active tasks.

chain_keys = splitrng(key, Val(4))
chain_values = map(chain_keys) do chain_key
    library_estimate(MutableRNG(chain_key), 128)
end

length(unique(chain_values)) == length(chain_values)
true

See 06_ecosystem_boundary.jl and 18_turing_bridge.jl.