Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introducing unit result in grounded functions #452

Merged
merged 2 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 33 additions & 23 deletions lib/src/metta/runner/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ use super::arithmetics::*;

pub const VOID_SYMBOL : Atom = sym!("%void%");

pub fn UNIT_ATOM() -> Atom {
Atom::expr([])
}
pub fn UNIT_TYPE() -> Atom {
Atom::expr([ARROW_SYMBOL])
}
fn unit_result() -> Result<Vec<Atom>, ExecError> {
Ok(vec![UNIT_ATOM()])
}

// TODO: remove hiding errors completely after making it possible passing
// them to the user
fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result<Vec<Atom>, String> {
Expand Down Expand Up @@ -52,7 +62,7 @@ impl Display for ImportOp {

impl Grounded for ImportOp {
fn type_(&self) -> Atom {
ATOM_TYPE_UNDEFINED
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE()])
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Expand Down Expand Up @@ -117,7 +127,7 @@ impl Grounded for ImportOp {
},
_ => return Err("import! expects space as a first argument".into()),
};
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -173,7 +183,7 @@ impl Display for BindOp {

impl Grounded for BindOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SYMBOL, ATOM_TYPE_UNDEFINED, ATOM_TYPE_UNDEFINED])
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()])
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Expand All @@ -183,7 +193,7 @@ impl Grounded for BindOp {

let token_regex = Regex::new(token).map_err(|err| format!("Could convert token {} into regex: {}", token, err))?;
self.tokenizer.borrow_mut().register_token(token_regex, move |_| { atom.clone() });
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -231,7 +241,7 @@ impl Display for AddAtomOp {
impl Grounded for AddAtomOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, rust_type_atom::<DynSpace>(),
ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED])
ATOM_TYPE_ATOM, UNIT_TYPE()])
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Expand All @@ -240,7 +250,7 @@ impl Grounded for AddAtomOp {
let atom = args.get(1).ok_or_else(arg_error)?;
let space = Atom::as_gnd::<DynSpace>(space).ok_or("add-atom expects a space as the first argument")?;
space.borrow_mut().add(atom.clone());
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand All @@ -260,7 +270,7 @@ impl Display for RemoveAtomOp {
impl Grounded for RemoveAtomOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, rust_type_atom::<DynSpace>(),
ATOM_TYPE_ATOM, ATOM_TYPE_ATOM])
ATOM_TYPE_ATOM, UNIT_TYPE()])
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Expand All @@ -269,8 +279,8 @@ impl Grounded for RemoveAtomOp {
let atom = args.get(1).ok_or_else(arg_error)?;
let space = Atom::as_gnd::<DynSpace>(space).ok_or("remove-atom expects a space as the first argument")?;
space.borrow_mut().remove(atom);
// TODO? return Bool
Ok(vec![])
// TODO? Is it necessary to distinguish whether the atom was removed or not?
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -507,7 +517,7 @@ fn assert_results_equal(actual: &Vec<Atom>, expected: &Vec<Atom>, atom: &Atom) -
log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom);
let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual);
match vec_eq_no_order(actual.iter(), expected.iter()) {
Ok(()) => Ok(vec![]),
Ok(()) => unit_result(),
Err(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff)))
}
}
Expand Down Expand Up @@ -696,7 +706,7 @@ impl Grounded for PragmaOp {
let key = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "pragma! expects symbol atom as a key")?.name();
let value = args.get(1).ok_or_else(arg_error)?;
self.settings.borrow_mut().insert(key.into(), value.clone());
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -749,14 +759,14 @@ impl Display for PrintlnOp {

impl Grounded for PrintlnOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, sym!("IO")])
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()])
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("println! expects single atom as an argument");
let atom = args.get(0).ok_or_else(arg_error)?;
println!("{}", atom);
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -838,7 +848,7 @@ impl Grounded for NopOp {
}

fn execute(&self, _args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Ok(vec![])
unit_result()
}

fn match_(&self, other: &Atom) -> MatchResultIter {
Expand Down Expand Up @@ -1224,7 +1234,7 @@ mod tests {
let space = DynSpace::new(GroundingSpace::new());
let satom = Atom::gnd(space.clone());
let res = AddAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned");
assert!(res.is_empty());
assert_eq!(res, vec![UNIT_ATOM()]);
let space_atoms: Vec<Atom> = space.borrow().as_space().atom_iter().unwrap().cloned().collect();
assert_eq_no_order!(space_atoms, vec![expr!(("foo" "bar"))]);
}
Expand All @@ -1238,7 +1248,7 @@ mod tests {
let satom = Atom::gnd(space.clone());
let res = RemoveAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned");
// REM: can return Bool in future
assert!(res.is_empty());
assert_eq!(res, vec![UNIT_ATOM()]);
let space_atoms: Vec<Atom> = space.borrow().as_space().atom_iter().unwrap().cloned().collect();
assert_eq_no_order!(space_atoms, vec![expr!(("bar" "foo"))]);
}
Expand Down Expand Up @@ -1286,7 +1296,7 @@ mod tests {

let bind_op = BindOp::new(tokenizer.clone());

assert_eq!(bind_op.execute(&mut vec![sym!("&my"), sym!("definition")]), Ok(vec![]));
assert_eq!(bind_op.execute(&mut vec![sym!("&my"), sym!("definition")]), unit_result());
let borrowed = tokenizer.borrow();
let constr = borrowed.find_token("&my");
assert!(constr.is_some());
Expand Down Expand Up @@ -1349,7 +1359,7 @@ mod tests {

let assert_equal_op = AssertEqualOp::new(space);

assert_eq!(assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("bar"))]), Ok(vec![]));
assert_eq!(assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("bar"))]), unit_result());

let actual = assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("err"))]);
let expected = Regex::new("\nExpected: \\[(A B)\\]\nGot: \\[\\((B C)|, |(A B)\\){3}\\]\nExcessive result: (B C)").unwrap();
Expand All @@ -1370,7 +1380,7 @@ mod tests {

assert_eq!(assert_equal_to_result_op.execute(&mut vec![
expr!(("foo")), expr!(("B" "C") ("A" "B"))]),
Ok(vec![]));
unit_result());
}

#[test]
Expand Down Expand Up @@ -1444,11 +1454,11 @@ mod tests {
(: a A)
(: b B)

!(superpose ((f (nop)) (f a) (f b)))
!(superpose ((f (superpose ())) (f a) (f b)))
");

assert_eq!(metta.run(&mut parser), Ok(vec![vec![
expr!("Error" ("f" ({NopOp{}})) "NoValidAlternatives"),
expr!("Error" ("f" ({SuperposeOp{space:metta.space().clone()}} ())) "NoValidAlternatives"),
expr!("a"), expr!("Error" "b" "BadType")]]));
}

Expand Down Expand Up @@ -1483,7 +1493,7 @@ mod tests {

#[test]
fn println_op() {
assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), Ok(vec![]));
assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), unit_result());
}

#[test]
Expand All @@ -1494,7 +1504,7 @@ mod tests {

#[test]
fn nop_op() {
assert_eq!(NopOp{}.execute(&mut vec![]), Ok(vec![]));
assert_eq!(NopOp{}.execute(&mut vec![]), unit_result());
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion lib/tests/metta.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use hyperon::metta::runner::stdlib::UNIT_ATOM;
use hyperon::metta::text::*;
use hyperon::metta::runner::new_metta_rust;

Expand All @@ -17,5 +18,5 @@ fn test_reduce_higher_order() {

let result = metta.run(&mut SExprParser::new(program));

assert_eq!(result, Ok(vec![vec![]]));
assert_eq!(result, Ok(vec![vec![UNIT_ATOM()]]));
}
5 changes: 3 additions & 2 deletions python/tests/scripts/c1_grounded_basic.metta
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@
; Non-determinism "reasoning":
; Among all 3-bit binary lists, return the one whose `subsum`
; with (:: 3 (:: 7 (:: 5 nil))) equals 8, or `nop` if not found
; (`nop` is a grounded function that consumes its arguments and returns nothing)
; (`superpose` is used to return an empty result acting as termination
; of evaluation of the branch)
!(assertEqualToResult
(let $t (gen 3)
(if (== (subsum (:: 3 (:: 7 (:: 5 nil))) $t) 8) $t (nop)))
(if (== (subsum (:: 3 (:: 7 (:: 5 nil))) $t) 8) $t (superpose ())))
((:: 1 (:: 0 (:: 1 nil)))))
7 changes: 5 additions & 2 deletions python/tests/scripts/e1_kb_write.metta
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
(= (ift True $then) $then)

; For anything that is green, assert it is Green in &kb
!(ift (green $x)
(add-atom &kb (Green $x)))
; There should be two green things
!(assertEqualToResult
(ift (green $x)
(add-atom &kb (Green $x)))
(() ()))

; Retrieve the inferred Green things: Fritz and Sam.
!(assertEqualToResult
Expand Down
4 changes: 2 additions & 2 deletions python/tests/scripts/e2_states.metta
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Changing the content of the state atom
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (nop ...) provides a way to obtain side-effects of a statement
; while ignoring unwanted return values.
; nop is used to ignore the result and return unit
; as expected by unit tests
!(nop (change-state! &state-token (C D)))

; The same state atom has different content now
Expand Down
6 changes: 2 additions & 4 deletions python/tests/test_run_metta.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import unittest

from hyperon import MeTTa, Atoms
from hyperon import MeTTa, E
from test_common import HyperonTestCase

from pathlib import Path
Expand Down Expand Up @@ -72,7 +70,7 @@ def test_comments(self):

def process_exceptions(self, results):
for result in results:
self.assertEqual(result, [])
self.assertEqual(result, [E()])

def test_scripts(self):
self.process_exceptions(MeTTa().import_file(f"{pwd}/scripts/a1_symbols.metta"))
Expand Down
Loading