Repository: trueagi-io/hyperon-experimental
Pull request #931: random to module
Author: login: DaddyWesker
Created: 2025-05-14T08:59:24Z | Merged: 2025-05-15T10:27:31Z

I moved random from stdlib to built-in module as we've discussed with @vsbogd . Currently, two commented-out tests not working since 
`!(assertEqual (random-int &rng 5 0) (Error (random-int &rng 5 0) RangeIsEmpty))`

now gives
`[(Error (assertEqual (random-int RandomGenerator-0x62962c56f410 5 0)  (Error (random-int RandomGenerator-0x62962c5758f0 5 0) RangeIsEmpty)) IncorrectNumberOfArguments)]`

Probably I'm missing something since this test was working properly in previous state of random.rs.


Diff:
diff --git a/lib/src/atom/gnd.rs b/lib/src/atom/gnd.rs
index 1140fa8ce..5659a01aa 100644
--- a/lib/src/atom/gnd.rs
+++ b/lib/src/atom/gnd.rs
@@ -72,11 +72,7 @@ impl<T: GroundedFunction> Grounded for GroundedFunctionAtom<T> {
 
 impl<T: GroundedFunction> CustomExecute for GroundedFunctionAtom<T> {
     fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        match self.0.func.execute(args) {
-            Err(ExecError::Runtime(msg)) => Err(ExecError::Runtime(format!("{}: {}", self, msg))),
-            Err(err) => Err(err),
-            Ok(res) => Ok(res),
-        }
+        self.0.func.execute(args)
     }
 }
 
diff --git a/lib/src/metta/runner/builtin_mods/mod.rs b/lib/src/metta/runner/builtin_mods/mod.rs
index 9f9bbfabc..2224e005e 100644
--- a/lib/src/metta/runner/builtin_mods/mod.rs
+++ b/lib/src/metta/runner/builtin_mods/mod.rs
@@ -4,12 +4,14 @@ use crate::metta::runner::Metta;
 /// Skeleton of the built-in module
 mod skel;
 mod fileio;
+mod random;
 /// Op atoms for working with catalogs
 #[cfg(feature = "pkg_mgmt")]
 pub mod catalog_mods;
 
 pub fn load_builtin_mods(metta: &Metta) -> Result<(), String> {
     // Built-in modules are loaded on the MeTTa initialization stage but not imported automatically
+    let _mod_id = metta.load_module_direct(Box::new(random::RandomModLoader), "random").map_err(|e| format!("error loading builtin \"catalog\" module: {e}"))?;
     let _mod_id = metta.load_module_direct(Box::new(skel::SkelModLoader), "skel").map_err(|e| format!("error loading builtin \"catalog\" module: {e}"))?;
     let _mod_id = metta.load_module_direct(Box::new(fileio::FileioModLoader), "fileio").map_err(|e| format!("error loading builtin \"catalog\" module: {e}"))?;
     #[cfg(feature = "pkg_mgmt")]
diff --git a/lib/src/metta/runner/builtin_mods/random.metta b/lib/src/metta/runner/builtin_mods/random.metta
new file mode 100644
index 000000000..13c0ea577
--- /dev/null
+++ b/lib/src/metta/runner/builtin_mods/random.metta
@@ -0,0 +1,39 @@
+(@doc random-int
+  (@desc "Returns random int number from range defined by two numbers (second and third argument)")
+  (@params (
+    (@param "Random number generator instance")
+    (@param "Range start")
+    (@param "Range end")))
+  (@return "Random int number from defined range"))
+
+(@doc random-float
+  (@desc "Returns random float number from range defined by two numbers (second and third argument)")
+  (@params (
+    (@param "Random number generator instance")
+    (@param "Range start")
+    (@param "Range end")))
+  (@return "Random float number from defined range"))
+
+(@doc set-random-seed
+  (@desc "Sets a new seed (second argument) for random number generator (first argument)")
+  (@params (
+    (@param "Random number generator instance")
+    (@param "Seed")))
+  (@return "Unit atom"))
+
+(@doc new-random-generator
+  (@desc "Creates new random number generator instance using seed as input (first argument)")
+  (@params (
+    (@param "Seed")))
+  (@return "Instance of random number generator"))
+
+(@doc reset-random-generator
+  (@desc "Resets instance of random number generator (first argument) to its default behavior (StdRng::from_os_rng())")
+  (@params (
+    (@param "Random number generator instance")))
+  (@return "Random number generator instance with default behavior"))
+
+(@doc flip
+  (@desc "Random boolean value")
+  (@params ())
+  (@return "Returns uniformly distributed random boolean value"))
\ No newline at end of file
diff --git a/lib/src/metta/runner/builtin_mods/random.rs b/lib/src/metta/runner/builtin_mods/random.rs
new file mode 100644
index 000000000..7fb045308
--- /dev/null
+++ b/lib/src/metta/runner/builtin_mods/random.rs
@@ -0,0 +1,293 @@
+
+use crate::*;
+use crate::metta::*;
+use crate::metta::text::SExprParser;
+use crate::metta::runner::number::*;
+use crate::metta::runner::bool::*;
+
+use std::fmt::{Display, Formatter};
+use std::cell::RefCell;
+use rand::{Rng, SeedableRng, rngs::StdRng};
+use std::rc::Rc;
+use crate::gnd::GroundedFunctionAtom;
+use crate::metta::runner::modules::{MettaMod, ModuleLoader};
+use crate::metta::runner::{Metta, RunContext};
+
+use regex::Regex;
+//TODO: In the current version of rand it is possible for rust to hang if range end's value is too
+// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be
+// possible to use match construction to cover overflow and other errors. So after library will be
+// upgraded RandomInt and RandomFloat codes should be altered.
+// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414
+
+pub static RANDOM_METTA: &'static str = include_str!("random.metta");
+
+pub const ATOM_TYPE_RANDOM_GENERATOR : Atom = sym!("RandomGenerator");
+
+#[derive(Clone, Debug)]
+pub struct RandomGenerator(Rc<RefCell<StdRng>>);
+
+impl RandomGenerator {
+    fn from_os_rng() -> Self {
+        Self(Rc::new(RefCell::new(StdRng::from_os_rng())))
+    }
+
+    fn from_seed_u64(seed: u64) -> Self {
+        Self(Rc::new(RefCell::new(StdRng::seed_from_u64(seed))))
+    }
+
+    fn reseed_from_u64(&self, seed: u64) {
+        *self.0.borrow_mut() = StdRng::seed_from_u64(seed);
+    }
+
+    fn reset(&self) {
+        *self.0.borrow_mut() = StdRng::from_os_rng();
+    }
+
+    fn random_range<T, R>(&self, range: R) -> T 
+        where
+            T: rand::distr::uniform::SampleUniform,
+            R: rand::distr::uniform::SampleRange<T>,
+    {
+        self.0.borrow_mut().random_range(range)
+    }
+}
+
+impl Grounded for RandomGenerator {
+    fn type_(&self) -> Atom {
+        ATOM_TYPE_RANDOM_GENERATOR 
+    }
+}
+
+impl Display for RandomGenerator {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "RandomGenerator-{:?}", self.0.as_ptr())
+    }
+}
+
+impl PartialEq for RandomGenerator {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.as_ptr() == other.0.as_ptr()
+    }
+}
+
+
+#[derive(Debug)]
+pub(crate) struct RandomModLoader;
+
+impl ModuleLoader for RandomModLoader {
+    fn load(&self, context: &mut RunContext) -> Result<(), String> {
+        // Initialize module's space
+        let space = GroundingSpace::new();
+        context.init_self_module(space.into(), None);
+
+        // Load module's tokens
+        let _ = self.load_tokens(context.module(), context.metta.clone())?;
+
+        // Parse MeTTa code of the module
+        let parser = SExprParser::new(RANDOM_METTA);
+        context.push_parser(Box::new(parser));
+
+        Ok(())
+    }
+
+    fn load_tokens(&self, target: &MettaMod, _metta: Metta) -> Result<(), String> {
+        let mut tref = target.tokenizer().borrow_mut();
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-int".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_int));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-float".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_float));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"set-random-seed".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, UNIT_TYPE]),
+            set_random_seed));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"new-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_RANDOM_GENERATOR]),
+            new_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"reset-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, UNIT_ATOM]),
+            reset_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"flip".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL]),
+            flip));
+
+        let generator = Atom::gnd(RandomGenerator::from_os_rng());
+        tref.register_token(Regex::new(r"&rng").unwrap(),move |_| { generator.clone() });
+
+        Ok(())
+    }
+}
+
+
+fn random_int(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    let arg_error = || ExecError::from("random-int expects three arguments: random generator, number (start) and number (end)");
+    let generator = args.get(0).ok_or_else(arg_error)?.into();
+    let start: i64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let end: i64 = args.get(2).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("random-int expects a random generator as its argument")?;
+    let range = start..end;
+    if range.is_empty() {
+        return Err(ExecError::from("RangeIsEmpty"));
+    }
+    Ok(vec![Atom::gnd(Number::Integer(generator.random_range(range)))])
+}
+
+fn random_float(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    let arg_error = || ExecError::from("random-float expects three arguments: random generator, number (start) and number (end)");
+    let generator = args.get(0).ok_or_else(arg_error)?.into();
+    let start: f64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let end: f64 = args.get(2).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("random-float expects a random generator as its argument")?;
+    let range = start..end;
+    if range.is_empty() {
+        return Err(ExecError::from("RangeIsEmpty"));
+    }
+    Ok(vec![Atom::gnd(Number::Float(generator.random_range(range)))])
+}
+
+fn set_random_seed(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    let arg_error = || ExecError::from("set-random-seed expects two arguments: random generator and number (seed)");
+    let generator = args.get(0).ok_or_else(arg_error)?.into();
+    let seed: i64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("set-random-seed expects a random generator as its argument")?;
+    generator.reseed_from_u64(seed as u64);
+    Ok(vec![UNIT_ATOM])
+}
+
+fn new_random_generator(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    let arg_error = || ExecError::from("new-random-generator expects one argument: number (seed)");
+    let seed: i64 = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
+    let new_generator = RandomGenerator::from_seed_u64(seed as u64);
+    Ok(vec![Atom::gnd(new_generator)])
+}
+
+fn reset_random_generator(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    let arg_error = || ExecError::from("reset-random-generator expects one argument: random generator");
+    let generator = args.get(0).ok_or_else(arg_error)?.into();
+    let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("set-random-seed expects a random generator as its argument")?;
+    generator.reset();
+    Ok(vec![UNIT_ATOM])
+}
+
+// NOTE: flip is absent in Python intentionally for conversion testing
+fn flip(_args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
+    Ok(vec![Atom::gnd(Bool(rand::random()))])
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::metta::runner::run_program;
+
+    #[test]
+    fn metta_random() {
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(chain (eval (random-int &rng 0 5)) $rint
+                (and (>= $rint 0) (< $rint 5)))")),
+            Ok(vec![vec![UNIT_ATOM], vec![expr!({Bool(true)})]]));
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(assertEqual
+                (random-int &rng 5 0)
+                (Error (random-int &rng 5 0) RangeIsEmpty))")),
+            Ok(vec![vec![UNIT_ATOM], vec![UNIT_ATOM]]));
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(chain (eval (random-float &rng 0.0 5.0)) $rfloat
+                (and (>= $rfloat 0.0) (< $rfloat 5.0)))")),
+            Ok(vec![vec![UNIT_ATOM], vec![expr!({Bool(true)})]]));
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(assertEqual
+                (random-float &rng 5 0)
+                (Error (random-float &rng 5 0) RangeIsEmpty))")),
+            Ok(vec![vec![UNIT_ATOM], vec![UNIT_ATOM]]));
+
+        assert_eq!(run_program(&format!("
+            !(import! &self random)
+            !(set-random-seed &rng 0)")),
+            Ok(vec![vec![UNIT_ATOM], vec![UNIT_ATOM]]));
+
+        assert_eq!(run_program(&format!("
+            !(import! &self random)
+            !(bind! &newrng (new-random-generator 0)) 
+            !(random-float &newrng 0 10)
+        ")), run_program(&format!("
+            !(import! &self random)
+            !(bind! &newrng (new-random-generator 0)) 
+            !(random-float &newrng 0 10)
+        ")));
+
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(let $newrng (new-random-generator 0)
+                (let $t (set-random-seed $newrng 5)
+                    (let $res_1 (random-float $newrng 0 5)
+                        (let $t2 (set-random-seed $newrng 5)
+                            (let $res_2 (random-float $newrng 0 5)
+                                (== $res_1 $res_2))))))"
+            )), Ok(vec![vec![UNIT_ATOM], vec![expr!({Bool(true)})]]));
+
+        assert_eq!(run_program(&format!(
+            "!(import! &self random)
+             !(let $seededrng (new-random-generator 0)
+                (let $seededrng2 (new-random-generator 0)
+                    (let $t (reset-random-generator $seededrng)
+                        (let $rfloat (random-float $seededrng 0 100)
+                            (let $rfloat2 (random-float $seededrng2 0 100)
+                                (== $rfloat $rfloat2))))) )"
+            )), Ok(vec![vec![UNIT_ATOM], vec![expr!({Bool(false)})]]));
+
+        assert_eq!(run_program(&format!("
+            !(import! &self random)
+            !(let $newrng (new-random-generator 0)
+                (let $t (reset-random-generator $newrng)
+                    (let $res (random-float $newrng 0 5)
+                        (and (>= $res 0.0) (< $res 5.0)))))")),
+            Ok(vec![vec![UNIT_ATOM], vec![expr!({Bool(true)})]]));
+    }
+
+    #[test]
+    fn random_op() {
+        let res = random_int(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        let range = 0..5;
+        let res_i64: i64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
+        assert!(range.contains(&res_i64));
+        let res = random_int(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(2)}), expr!({Number::Integer(-2)})]);
+        assert_eq!(res, Err(ExecError::from("RangeIsEmpty")));
+
+        let res = random_float(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        let range = 0.0..5.0;
+        let res_f64: f64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
+        assert!(range.contains(&res_f64));
+        let res = random_float(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]);
+        assert_eq!(res, Err(ExecError::from("RangeIsEmpty")));
+
+        let gen = new_random_generator(&mut vec![expr!({Number::Integer(0)})]);
+        let res1 = random_float(&mut vec![expr!({gen}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        let gen = new_random_generator(&mut vec![expr!({Number::Integer(0)})]);
+        let res2 = random_float(&mut vec![expr!({gen}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        assert_eq!(res1, res2);
+
+        let gen = new_random_generator(&mut vec![expr!({Number::Integer(0)})]);
+        let _ = set_random_seed(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)})]);
+        let res1 = random_float(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        let _ = set_random_seed(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(5)})]);
+        let res2 = random_float(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
+        assert_eq!(res1, res2);
+    }
+}
diff --git a/lib/src/metta/runner/stdlib/mod.rs b/lib/src/metta/runner/stdlib/mod.rs
index 8979ab785..875fda744 100644
--- a/lib/src/metta/runner/stdlib/mod.rs
+++ b/lib/src/metta/runner/stdlib/mod.rs
@@ -1,6 +1,5 @@
 #[macro_use]
 pub mod math;
-pub mod random;
 pub mod atom;
 pub mod module;
 #[cfg(feature = "pkg_mgmt")]
@@ -100,7 +99,6 @@ fn register_context_independent_tokens(tref: &mut Tokenizer) {
     core::register_context_independent_tokens(tref);
     debug::register_context_independent_tokens(tref);
     math::register_context_independent_tokens(tref);
-    random::register_context_independent_tokens(tref);
     arithmetics::register_context_independent_tokens(tref);
     string::register_context_independent_tokens(tref);
     space::register_context_independent_tokens(tref);
diff --git a/lib/src/metta/runner/stdlib/random.rs b/lib/src/metta/runner/stdlib/random.rs
deleted file mode 100644
index 7812d89ff..000000000
--- a/lib/src/metta/runner/stdlib/random.rs
+++ /dev/null
@@ -1,339 +0,0 @@
-
-use crate::*;
-use crate::metta::*;
-use super::{grounded_op, regex};
-use crate::metta::text::Tokenizer;
-use crate::metta::runner::number::*;
-use crate::metta::runner::bool::*;
-
-use std::fmt::{Display, Formatter};
-use std::cell::RefCell;
-use rand::{Rng, SeedableRng, rngs::StdRng};
-use std::rc::Rc;
-
-//TODO: In the current version of rand it is possible for rust to hang if range end's value is too
-// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be
-// possible to use match construction to cover overflow and other errors. So after library will be
-// upgraded RandomInt and RandomFloat codes should be altered.
-// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414
-
-pub const ATOM_TYPE_RANDOM_GENERATOR : Atom = sym!("RandomGenerator");
-
-#[derive(Clone, Debug)]
-pub struct RandomGenerator(Rc<RefCell<StdRng>>);
-
-impl RandomGenerator {
-    fn from_os_rng() -> Self {
-        Self(Rc::new(RefCell::new(StdRng::from_os_rng())))
-    }
-
-    fn from_seed_u64(seed: u64) -> Self {
-        Self(Rc::new(RefCell::new(StdRng::seed_from_u64(seed))))
-    }
-
-    fn reseed_from_u64(&self, seed: u64) {
-        *self.0.borrow_mut() = StdRng::seed_from_u64(seed);
-    }
-
-    fn reset(&self) {
-        *self.0.borrow_mut() = StdRng::from_os_rng();
-    }
-
-    fn random_range<T, R>(&self, range: R) -> T 
-        where
-            T: rand::distr::uniform::SampleUniform,
-            R: rand::distr::uniform::SampleRange<T>,
-    {
-        self.0.borrow_mut().random_range(range)
-    }
-}
-
-impl Grounded for RandomGenerator {
-    fn type_(&self) -> Atom {
-        ATOM_TYPE_RANDOM_GENERATOR 
-    }
-}
-
-impl Display for RandomGenerator {
-    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
-        write!(f, "RandomGenerator-{:?}", self.0.as_ptr())
-    }
-}
-
-impl PartialEq for RandomGenerator {
-    fn eq(&self, other: &Self) -> bool {
-        self.0.as_ptr() == other.0.as_ptr()
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct RandomIntOp {}
-
-grounded_op!(RandomIntOp, "random-int");
-
-impl Grounded for RandomIntOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for RandomIntOp {
-    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        let arg_error = || ExecError::from("random-int expects three arguments: random generator, number (start) and number (end)");
-        let generator = args.get(0).ok_or_else(arg_error)?.into();
-        let start: i64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let end: i64 = args.get(2).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("random-int expects a random generator as its argument")?;
-        let range = start..end;
-        if range.is_empty() {
-            return Err(ExecError::from("RangeIsEmpty"));
-        }
-        Ok(vec![Atom::gnd(Number::Integer(generator.random_range(range)))])
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct RandomFloatOp {}
-
-grounded_op!(RandomFloatOp, "random-float");
-
-impl Grounded for RandomFloatOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for RandomFloatOp {
-    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        let arg_error = || ExecError::from("random-float expects three arguments: random generator, number (start) and number (end)");
-        let generator = args.get(0).ok_or_else(arg_error)?.into();
-        let start: f64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let end: f64 = args.get(2).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("random-float expects a random generator as its argument")?;
-        let range = start..end;
-        if range.is_empty() {
-            return Err(ExecError::from("RangeIsEmpty"));
-        }
-        Ok(vec![Atom::gnd(Number::Float(generator.random_range(range)))])
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct SetRandomSeedOp {}
-
-grounded_op!(SetRandomSeedOp, "set-random-seed");
-
-impl Grounded for SetRandomSeedOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, UNIT_TYPE])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for SetRandomSeedOp {
-    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        let arg_error = || ExecError::from("set-random-seed expects two arguments: random generator and number (seed)");
-        let generator = args.get(0).ok_or_else(arg_error)?.into();
-        let seed: i64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("set-random-seed expects a random generator as its argument")?;
-        generator.reseed_from_u64(seed as u64);
-        Ok(vec![UNIT_ATOM])
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct NewRandomGeneratorOp {}
-
-grounded_op!(NewRandomGeneratorOp, "new-random-generator");
-
-impl Grounded for NewRandomGeneratorOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_RANDOM_GENERATOR])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for NewRandomGeneratorOp {
-    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        let arg_error = || ExecError::from("new-random-generator expects one argument: number (seed)");
-        let seed: i64 = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
-        let new_generator = RandomGenerator::from_seed_u64(seed as u64);
-        Ok(vec![Atom::gnd(new_generator)])
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct ResetRandomGeneratorOp {}
-
-grounded_op!(ResetRandomGeneratorOp, "reset-random-generator");
-
-impl Grounded for ResetRandomGeneratorOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, UNIT_ATOM])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for ResetRandomGeneratorOp {
-    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        let arg_error = || ExecError::from("reset-random-generator expects one argument: random generator");
-        let generator = args.get(0).ok_or_else(arg_error)?.into();
-        let generator = Atom::as_gnd::<RandomGenerator>(generator).ok_or("set-random-seed expects a random generator as its argument")?;
-        generator.reset();
-        Ok(vec![UNIT_ATOM])
-    }
-}
-
-
-// NOTE: flip is absent in Python intentionally for conversion testing
-#[derive(Clone, PartialEq, Debug)]
-pub struct FlipOp{}
-
-impl Display for FlipOp {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "flip")
-    }
-}
-
-impl Grounded for FlipOp {
-    fn type_(&self) -> Atom {
-        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL])
-    }
-
-    fn as_execute(&self) -> Option<&dyn CustomExecute> {
-        Some(self)
-    }
-}
-
-impl CustomExecute for FlipOp {
-    fn execute(&self, _args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
-        Ok(vec![Atom::gnd(Bool(rand::random()))])
-    }
-}
-
-pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
-    let random_int_op = Atom::gnd(RandomIntOp{});
-    tref.register_token(regex(r"random-int"), move |_| { random_int_op.clone() });
-    let random_float_op = Atom::gnd(RandomFloatOp{});
-    tref.register_token(regex(r"random-float"), move |_| { random_float_op.clone() });
-    let set_seed_op = Atom::gnd(SetRandomSeedOp{});
-    tref.register_token(regex(r"set-random-seed"), move |_| { set_seed_op.clone() });
-    let new_random_generator_op = Atom::gnd(NewRandomGeneratorOp{});
-    tref.register_token(regex(r"new-random-generator"), move |_| { new_random_generator_op.clone() });
-    let reset_random_generator_op = Atom::gnd(ResetRandomGeneratorOp{});
-    tref.register_token(regex(r"reset-random-generator"), move |_| { reset_random_generator_op.clone() });
-    let generator = RandomGenerator::from_os_rng();
-    tref.register_token(regex(r"&rng"), move |_| { Atom::gnd(generator.clone()) });
-    let flip_op = Atom::gnd(FlipOp{});
-    tref.register_token(regex(r"flip"), move |_| { flip_op.clone() });
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::metta::runner::run_program;
-
-    #[test]
-    fn metta_random() {
-        assert_eq!(run_program(&format!(
-            "!(chain (eval (random-int &rng 0 5)) $rint
-                (and (>= $rint 0) (< $rint 5)))")),
-            Ok(vec![vec![expr!({Bool(true)})]]));
-        assert_eq!(run_program(&format!(
-            "!(assertEqual
-                (random-int &rng 5 0)
-                (Error (random-int &rng 5 0) RangeIsEmpty))")),
-            Ok(vec![vec![UNIT_ATOM]]));
-        assert_eq!(run_program(&format!(
-            "!(chain (eval (random-float &rng 0.0 5.0)) $rfloat
-                (and (>= $rfloat 0.0) (< $rfloat 5.0)))")),
-            Ok(vec![vec![expr!({Bool(true)})]]));
-        assert_eq!(run_program(&format!(
-            "!(assertEqual
-                (random-float &rng 5 0)
-                (Error (random-float &rng 5 0) RangeIsEmpty))")),
-            Ok(vec![vec![UNIT_ATOM]]));
-
-        assert_eq!(run_program(&format!("!(set-random-seed &rng 0)")), Ok(vec![vec![UNIT_ATOM]]));
-
-        assert_eq!(run_program(&format!("
-            !(bind! &newrng (new-random-generator 0)) 
-            !(random-float &newrng 0 10)
-        ")), run_program(&format!("
-            !(bind! &newrng (new-random-generator 0)) 
-            !(random-float &newrng 0 10)
-        ")));
-
-        assert_eq!(run_program(&format!(
-            "!(let $newrng (new-random-generator 0)
-                (let $t (set-random-seed $newrng 5)
-                    (let $res_1 (random-float $newrng 0 5)
-                        (let $t2 (set-random-seed $newrng 5)
-                            (let $res_2 (random-float $newrng 0 5)
-                                (== $res_1 $res_2))))))"
-            )), Ok(vec![vec![expr!({Bool(true)})]]));
-
-        assert_eq!(run_program(&format!(
-            "!(let $seededrng (new-random-generator 0)
-                (let $seededrng2 (new-random-generator 0)
-                    (let $t (reset-random-generator $seededrng)
-                        (let $rfloat (random-float $seededrng 0 100)
-                            (let $rfloat2 (random-float $seededrng2 0 100)
-                                (== $rfloat $rfloat2))))) )"
-            )), Ok(vec![vec![expr!({Bool(false)})]]));
-
-        assert_eq!(run_program(&format!("
-            !(let $newrng (new-random-generator 0)
-                (let $t (reset-random-generator $newrng)
-                    (let $res (random-float $newrng 0 5)
-                        (and (>= $res 0.0) (< $res 5.0)))))")),
-            Ok(vec![vec![expr!({Bool(true)})]]));
-    }
-
-    #[test]
-    fn random_op() {
-        let res = RandomIntOp{}.execute(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        let range = 0..5;
-        let res_i64: i64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
-        assert!(range.contains(&res_i64));
-        let res = RandomIntOp{}.execute(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(2)}), expr!({Number::Integer(-2)})]);
-        assert_eq!(res, Err(ExecError::from("RangeIsEmpty")));
-
-        let res = RandomFloatOp{}.execute(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        let range = 0.0..5.0;
-        let res_f64: f64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
-        assert!(range.contains(&res_f64));
-        let res = RandomFloatOp{}.execute(&mut vec![expr!({RandomGenerator::from_os_rng()}), expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]);
-        assert_eq!(res, Err(ExecError::from("RangeIsEmpty")));
-
-        let gen = NewRandomGeneratorOp{}.execute(&mut vec![expr!({Number::Integer(0)})]);
-        let res1 = RandomFloatOp{}.execute(&mut vec![expr!({gen}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        let gen = NewRandomGeneratorOp{}.execute(&mut vec![expr!({Number::Integer(0)})]);
-        let res2 = RandomFloatOp{}.execute(&mut vec![expr!({gen}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        assert_eq!(res1, res2);
-
-        let gen = NewRandomGeneratorOp{}.execute(&mut vec![expr!({Number::Integer(0)})]);
-        let _ = SetRandomSeedOp{}.execute(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)})]);
-        let res1 = RandomFloatOp{}.execute(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        let _ = SetRandomSeedOp{}.execute(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(5)})]);
-        let res2 = RandomFloatOp{}.execute(&mut vec![expr!({gen.clone()}), expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
-        assert_eq!(res1, res2);
-    }
-}
diff --git a/lib/src/metta/runner/stdlib/stdlib.metta b/lib/src/metta/runner/stdlib/stdlib.metta
index fb24d1cb6..65498d601 100644
--- a/lib/src/metta/runner/stdlib/stdlib.metta
+++ b/lib/src/metta/runner/stdlib/stdlib.metta
@@ -203,41 +203,6 @@
     (@param "Number")))
   (@return "True/False"))
 
-(@doc random-int
-  (@desc "Returns random int number from range defined by two numbers (second and third argument)")
-  (@params (
-    (@param "Random number generator instance")
-    (@param "Range start")
-    (@param "Range end")))
-  (@return "Random int number from defined range"))
-
-(@doc random-float
-  (@desc "Returns random float number from range defined by two numbers (second and third argument)")
-  (@params (
-    (@param "Random number generator instance")
-    (@param "Range start")
-    (@param "Range end")))
-  (@return "Random float number from defined range"))
-
-(@doc set-random-seed
-  (@desc "Sets a new seed (second argument) for random number generator (first argument)")
-  (@params (
-    (@param "Random number generator instance")
-    (@param "Seed")))
-  (@return "Unit atom"))
-
-(@doc new-random-generator
-  (@desc "Creates new random number generator instance using seed as input (first argument)")
-  (@params (
-    (@param "Seed")))
-  (@return "Instance of random number generator"))
-
-(@doc reset-random-generator
-  (@desc "Resets instance of random number generator (first argument) to its default behavior (StdRng::from_os_rng())")
-  (@params (
-    (@param "Random number generator instance")))
-  (@return "Random number generator instance with default behavior"))
-
 (@doc collapse-bind
   (@desc "Evaluates minimal MeTTa operation (first argument) and returns an expression which contains all alternative evaluations in a form (Atom Bindings). Bindings are represented in a form of a grounded atom.")
   (@params (
@@ -1228,11 +1193,6 @@ Possible pragmas:
     (@param "Second argument")))
   (@return "Returns True if one and only one of the inputs is True"))
 
-(@doc flip
-  (@desc "Random boolean value")
-  (@params ())
-  (@return "Returns uniformly distributed random boolean value"))
-
 (@doc unique-atom
   (@desc "Function takes tuple and returns only unique entities. E.g. (unique-atom (a b c d d)) -> (a b c d)")
   (@params (
diff --git a/python/sandbox/test_gnd_conv.metta b/python/sandbox/test_gnd_conv.metta
index d0d715aa4..3d74c4c26 100644
--- a/python/sandbox/test_gnd_conv.metta
+++ b/python/sandbox/test_gnd_conv.metta
@@ -1,3 +1,5 @@
+!(import! &self random)
+
 ; should work as passing Rust Bool to Rust function
 !(xor (flip) (flip))
 
diff --git a/python/tests/test_grounded_type.py b/python/tests/test_grounded_type.py
index 78e67c135..af8d05d11 100644
--- a/python/tests/test_grounded_type.py
+++ b/python/tests/test_grounded_type.py
@@ -153,12 +153,13 @@ def test_undefined_operation_type(self):
 
     def test_conversion_between_rust_and_python(self):
         metta = MeTTa(env_builder=Environment.test_env())
+        metta.run('!(import! &self random)', flat=True)
         integer = metta.run('!(+ 1 (random-int &rng 4 5))', flat=True)[0].get_object()
         self.assertEqual(integer, ValueObject(5))
         float = metta.run('!(+ 1.0 (random-float &rng 4 5))', flat=True)[0].get_object()
         self.assertTrue(float.value >= 5.0 and float.value < 6)
         bool = metta.run('!(not (flip))', flat=True)[0].get_object()
-        self.assertTrue(bool.value or  not bool.value)
+        self.assertTrue(bool.value or not bool.value)
         false = metta.run('!(not True)', flat=True)[0].get_object()
         self.assertEqual(false, ValueObject(False))
 


Reviews:
user: login: vsbogd
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T14:17:14Z
user: login: vsbogd
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T10:27:15Z

Review comments:
diff_hunk: @@ -0,0 +1,292 @@
+
+use crate::*;
+use crate::metta::*;
+use crate::metta::text::SExprParser;
+use crate::metta::runner::number::*;
+use crate::metta::runner::bool::*;
+
+use std::fmt::{Display, Formatter};
+use std::cell::RefCell;
+use rand::{Rng, SeedableRng, rngs::StdRng};
+use std::rc::Rc;
+use crate::gnd::GroundedFunctionAtom;
+use crate::metta::runner::modules::{MettaMod, ModuleLoader};
+use crate::metta::runner::{Metta, RunContext};
+
+use regex::Regex;
+//TODO: In the current version of rand it is possible for rust to hang if range end's value is too
+// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be
+// possible to use match construction to cover overflow and other errors. So after library will be
+// upgraded RandomInt and RandomFloat codes should be altered.
+// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414
+
+pub static RANDOM_METTA: &'static str = include_str!("random.metta");
+
+pub const ATOM_TYPE_RANDOM_GENERATOR : Atom = sym!("RandomGenerator");
+
+#[derive(Clone, Debug)]
+pub struct RandomGenerator(Rc<RefCell<StdRng>>);
+
+impl RandomGenerator {
+    fn from_os_rng() -> Self {
+        Self(Rc::new(RefCell::new(StdRng::from_os_rng())))
+    }
+
+    fn from_seed_u64(seed: u64) -> Self {
+        Self(Rc::new(RefCell::new(StdRng::seed_from_u64(seed))))
+    }
+
+    fn reseed_from_u64(&self, seed: u64) {
+        *self.0.borrow_mut() = StdRng::seed_from_u64(seed);
+    }
+
+    fn reset(&self) {
+        *self.0.borrow_mut() = StdRng::from_os_rng();
+    }
+
+    fn random_range<T, R>(&self, range: R) -> T 
+        where
+            T: rand::distr::uniform::SampleUniform,
+            R: rand::distr::uniform::SampleRange<T>,
+    {
+        self.0.borrow_mut().random_range(range)
+    }
+}
+
+impl Grounded for RandomGenerator {
+    fn type_(&self) -> Atom {
+        ATOM_TYPE_RANDOM_GENERATOR 
+    }
+}
+
+impl Display for RandomGenerator {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "RandomGenerator-{:?}", self.0.as_ptr())
+    }
+}
+
+impl PartialEq for RandomGenerator {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.as_ptr() == other.0.as_ptr()
+    }
+}
+
+
+#[derive(Debug)]
+pub(crate) struct RandomModLoader;
+
+impl ModuleLoader for RandomModLoader {
+    fn load(&self, context: &mut RunContext) -> Result<(), String> {
+        // Initialize module's space
+        let space = GroundingSpace::new();
+        context.init_self_module(space.into(), None);
+
+        // Load module's tokens
+        let _ = self.load_tokens(context.module(), context.metta.clone())?;
+
+        // Parse MeTTa code of the module
+        let parser = SExprParser::new(RANDOM_METTA);
+        context.push_parser(Box::new(parser));
+
+        Ok(())
+    }
+
+    fn load_tokens(&self, target: &MettaMod, _metta: Metta) -> Result<(), String> {
+        let mut tref = target.tokenizer().borrow_mut();
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-int".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_int));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-float".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_float));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"set-random-seed".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, UNIT_TYPE]),
+            set_random_seed));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"new-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_RANDOM_GENERATOR]),
+            new_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"reset-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, UNIT_ATOM]),
+            reset_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"flip".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL]),
+            flip));
+
+        tref.register_token(Regex::new(r"&rng").unwrap(),move |_| { Atom::gnd(RandomGenerator::from_os_rng()) });
path: lib/src/metta/runner/builtin_mods/random.rs
user: login: vsbogd
body: First reason why test fails is that you changed the way `&rng` is instantiated. Before it was a shared link to the same instance:
```
    let generator = RandomGenerator::from_os_rng();
    tref.register_token(regex(r"&rng"), move |_| { Atom::gnd(generator.clone()) });
```
Now it is new instance each time.
```suggestion
    let generator = RandomGenerator::from_os_rng();
    tref.register_token(Regex::new(r"&rng").unwrap(), move |_| { Atom::gnd(generator.clone()) });
```
created_at: 2025-05-14T14:07:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,292 @@
+
+use crate::*;
+use crate::metta::*;
+use crate::metta::text::SExprParser;
+use crate::metta::runner::number::*;
+use crate::metta::runner::bool::*;
+
+use std::fmt::{Display, Formatter};
+use std::cell::RefCell;
+use rand::{Rng, SeedableRng, rngs::StdRng};
+use std::rc::Rc;
+use crate::gnd::GroundedFunctionAtom;
+use crate::metta::runner::modules::{MettaMod, ModuleLoader};
+use crate::metta::runner::{Metta, RunContext};
+
+use regex::Regex;
+//TODO: In the current version of rand it is possible for rust to hang if range end's value is too
+// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be
+// possible to use match construction to cover overflow and other errors. So after library will be
+// upgraded RandomInt and RandomFloat codes should be altered.
+// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414
+
+pub static RANDOM_METTA: &'static str = include_str!("random.metta");
+
+pub const ATOM_TYPE_RANDOM_GENERATOR : Atom = sym!("RandomGenerator");
+
+#[derive(Clone, Debug)]
+pub struct RandomGenerator(Rc<RefCell<StdRng>>);
+
+impl RandomGenerator {
+    fn from_os_rng() -> Self {
+        Self(Rc::new(RefCell::new(StdRng::from_os_rng())))
+    }
+
+    fn from_seed_u64(seed: u64) -> Self {
+        Self(Rc::new(RefCell::new(StdRng::seed_from_u64(seed))))
+    }
+
+    fn reseed_from_u64(&self, seed: u64) {
+        *self.0.borrow_mut() = StdRng::seed_from_u64(seed);
+    }
+
+    fn reset(&self) {
+        *self.0.borrow_mut() = StdRng::from_os_rng();
+    }
+
+    fn random_range<T, R>(&self, range: R) -> T 
+        where
+            T: rand::distr::uniform::SampleUniform,
+            R: rand::distr::uniform::SampleRange<T>,
+    {
+        self.0.borrow_mut().random_range(range)
+    }
+}
+
+impl Grounded for RandomGenerator {
+    fn type_(&self) -> Atom {
+        ATOM_TYPE_RANDOM_GENERATOR 
+    }
+}
+
+impl Display for RandomGenerator {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "RandomGenerator-{:?}", self.0.as_ptr())
+    }
+}
+
+impl PartialEq for RandomGenerator {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.as_ptr() == other.0.as_ptr()
+    }
+}
+
+
+#[derive(Debug)]
+pub(crate) struct RandomModLoader;
+
+impl ModuleLoader for RandomModLoader {
+    fn load(&self, context: &mut RunContext) -> Result<(), String> {
+        // Initialize module's space
+        let space = GroundingSpace::new();
+        context.init_self_module(space.into(), None);
+
+        // Load module's tokens
+        let _ = self.load_tokens(context.module(), context.metta.clone())?;
+
+        // Parse MeTTa code of the module
+        let parser = SExprParser::new(RANDOM_METTA);
+        context.push_parser(Box::new(parser));
+
+        Ok(())
+    }
+
+    fn load_tokens(&self, target: &MettaMod, _metta: Metta) -> Result<(), String> {
+        let mut tref = target.tokenizer().borrow_mut();
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-int".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_int));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"random-float".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
+            random_float));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"set-random-seed".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, ATOM_TYPE_NUMBER, UNIT_TYPE]),
+            set_random_seed));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"new-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_RANDOM_GENERATOR]),
+            new_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"reset-random-generator".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_RANDOM_GENERATOR, UNIT_ATOM]),
+            reset_random_generator));
+
+        tref.register_function(GroundedFunctionAtom::new(
+            r"flip".into(),
+            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL]),
+            flip));
+
+        tref.register_token(Regex::new(r"&rng").unwrap(),move |_| { Atom::gnd(RandomGenerator::from_os_rng()) });
path: lib/src/metta/runner/builtin_mods/random.rs
user: login: vsbogd
body: Added this and common code fix in https://github.com/DaddyWesker/hyperon-experimental/pull/20
created_at: 2025-05-14T14:17:02Z
author_association: COLLABORATOR

PR comments:
user: login: vsbogd
created_at: 2025-05-15T06:26:35Z
body: @DaddyWesker please also fix `python/sandbox/test_gnd_conv.metta` which uses `flip` function and we can merge it.
author_association: COLLABORATOR
user: login: DaddyWesker
created_at: 2025-05-15T07:35:05Z
body: @vsbogd Done.
author_association: CONTRIBUTOR