Refactor random number generation to factory

- Use a `make-random` factory to create isolated random number
  generators.
- Remove the global `prng` from the `Environment`.
- Ensure `make-random` can be called with or without a seed.
This commit is contained in:
Michael Schimmel
2026-03-02 14:01:36 +01:00
parent f3459baf43
commit a4af142719
4 changed files with 49 additions and 38 deletions
+3 -3
View File
@@ -1,10 +1,10 @@
;; Benchmark: 2.0us ;; Benchmark: 3.0us
;; Benchmark-Repeat: 1015 ;; Benchmark-Repeat: 593
;; Output: PipelineNode[last: Some(110.45243843391206)] ;; Output: PipelineNode[last: Some(110.45243843391206)]
(do (do
;; Set the random seed to match the existing test output exactly ;; Set the random seed to match the existing test output exactly
(seed! 42) (def random (make-random 42))
;; Use our new generic create-ticker to pulse 3 times ;; Use our new generic create-ticker to pulse 3 times
(def cnt 3) (def cnt 3)
-2
View File
@@ -28,7 +28,6 @@ pub struct Environment {
pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>, pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
pub global_purity: Rc<RefCell<HashMap<GlobalIdx, Purity>>>, pub global_purity: Rc<RefCell<HashMap<GlobalIdx, Purity>>>,
pub global_values: Rc<RefCell<Vec<Value>>>, pub global_values: Rc<RefCell<Vec<Value>>>,
pub prng: Rc<RefCell<fastrand::Rng>>,
pub function_registry: Rc<RefCell<HashMap<GlobalIdx, BoundNode>>>, pub function_registry: Rc<RefCell<HashMap<GlobalIdx, BoundNode>>>,
pub typed_function_registry: Rc<RefCell<HashMap<GlobalIdx, AnalyzedNode>>>, pub typed_function_registry: Rc<RefCell<HashMap<GlobalIdx, AnalyzedNode>>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
@@ -102,7 +101,6 @@ impl Environment {
global_types: Rc::new(RefCell::new(HashMap::new())), global_types: Rc::new(RefCell::new(HashMap::new())),
global_purity: Rc::new(RefCell::new(HashMap::new())), global_purity: Rc::new(RefCell::new(HashMap::new())),
global_values: Rc::new(RefCell::new(Vec::new())), global_values: Rc::new(RefCell::new(Vec::new())),
prng: Rc::new(RefCell::new(fastrand::Rng::new())),
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_function_registry: Rc::new(RefCell::new(HashMap::new())), typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
+31 -19
View File
@@ -135,27 +135,39 @@ pub fn register(env: &Environment) {
best best
}); });
// Random // Random generator factory (Closure-based)
let random_ty = StaticType::Function(Box::new(Signature { let generator_sig = Signature {
params: StaticType::Tuple(vec![]), params: StaticType::Tuple(vec![]),
ret: StaticType::Float, ret: StaticType::Float,
})); };
let make_random_ty = StaticType::FunctionOverloads(vec![
Signature {
params: StaticType::Tuple(vec![]),
ret: StaticType::Function(Box::new(generator_sig.clone())),
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int]),
ret: StaticType::Function(Box::new(generator_sig)),
},
]);
let prng = env.prng.clone(); env.register_native_fn("make-random", make_random_ty, Purity::SideEffectFree, |args| {
env.register_native_fn("random", random_ty, Purity::SideEffectFree, move |_| { let rng = if args.is_empty() {
Value::Float(prng.borrow_mut().f64()) fastrand::Rng::new()
}); } else if let Value::Int(s) = args[0] {
fastrand::Rng::with_seed(s as u64)
let seed_ty = StaticType::Function(Box::new(Signature { } else {
params: StaticType::Tuple(vec![StaticType::Int]), fastrand::Rng::new()
ret: StaticType::Void, };
}));
let prng = std::rc::Rc::new(std::cell::RefCell::new(rng));
let prng_seed = env.prng.clone();
env.register_native_fn("seed!", seed_ty, Purity::Impure, move |args| { Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction {
if let Value::Int(s) = args[0] { func: std::rc::Rc::new(move |_| {
prng_seed.borrow_mut().seed(s as u64); Value::Float(prng.borrow_mut().f64())
} }),
Value::Void purity: Purity::Impure,
}))
}); });
} }
+15 -14
View File
@@ -175,12 +175,13 @@ mod tests {
let env1 = Environment::new(); let env1 = Environment::new();
let env2 = Environment::new(); let env2 = Environment::new();
// 1. Seed env1 // 1. Create a seeded generator in env1
env1.run_script("(seed! 123)").unwrap(); env1.run_script("(def rand (make-random 123))").unwrap();
let val1_a = env1.run_script("(random)").unwrap(); let val1_a = env1.run_script("(rand)").unwrap();
// 2. env2 should have its own default seed state // 2. env2 should have its own default seed state for its generators
let val2_a = env2.run_script("(random)").unwrap(); env2.run_script("(def rand (make-random))").unwrap();
let val2_a = env2.run_script("(rand)").unwrap();
// They are highly unlikely to be equal by default, // They are highly unlikely to be equal by default,
// and seeding env1 MUST not have seeded env2. // and seeding env1 MUST not have seeded env2.
@@ -189,9 +190,9 @@ mod tests {
"Environments must have isolated PRNG states" "Environments must have isolated PRNG states"
); );
// 3. Seed env2 differently // 3. Create another generator in env2 with the same seed
env2.run_script("(seed! 123)").unwrap(); env2.run_script("(def rand-same (make-random 123))").unwrap();
let val2_b = env2.run_script("(random)").unwrap(); let val2_b = env2.run_script("(rand-same)").unwrap();
// After same seeding, they should match (isolated but identical seed) // After same seeding, they should match (isolated but identical seed)
assert_eq!( assert_eq!(
@@ -205,12 +206,12 @@ mod tests {
let env = Environment::new(); let env = Environment::new();
// 1. First run with seed 42 // 1. First run with seed 42
env.run_script("(seed! 42)").unwrap(); env.run_script("(def rand1 (make-random 42))").unwrap();
let val1 = env.run_script("(random)").unwrap(); let val1 = env.run_script("(rand1)").unwrap();
// 2. Second run with same seed 42 // 2. Second run with same seed 42
env.run_script("(seed! 42)").unwrap(); env.run_script("(def rand2 (make-random 42))").unwrap();
let val2 = env.run_script("(random)").unwrap(); let val2 = env.run_script("(rand2)").unwrap();
assert_eq!( assert_eq!(
val1, val2, val1, val2,
@@ -218,8 +219,8 @@ mod tests {
); );
// 3. Third run with different seed // 3. Third run with different seed
env.run_script("(seed! 123)").unwrap(); env.run_script("(def rand3 (make-random 123))").unwrap();
let val3 = env.run_script("(random)").unwrap(); let val3 = env.run_script("(rand3)").unwrap();
assert_ne!(val1, val3, "Random results must differ for different seeds"); assert_ne!(val1, val3, "Random results must differ for different seeds");
} }