diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index aec13263b..ff036e047 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -898,10 +898,7 @@ pub struct BindingsSet(smallvec::SmallVec<[Bindings; 1]>); // BindingsSets are conceptually unordered impl PartialEq for BindingsSet { fn eq(&self, other: &Self) -> bool { - match crate::common::assert::vec_eq_no_order(self.iter(), other.iter()) { - Ok(()) => true, - Err(_) => false - } + !crate::common::assert::compare_vec_no_order(self.iter(), other.iter(), crate::common::collections::DefaultEquality{}).has_diff() } } @@ -1283,6 +1280,9 @@ mod test { #[test] fn test_atoms_are_equivalent() { + assert!(atoms_are_equivalent(&expr!(x "b" {"c"}), &expr!(x "b" {"c"}))); + assert!(atoms_are_equivalent(&expr!(x "b" x), &expr!(x "b" x))); + assert!(atoms_are_equivalent(&expr!(a a "b" {"c"}), &expr!(x x "b" {"c"}))); assert!(atoms_are_equivalent(&expr!(a "b" {"c"}), &expr!(x "b" {"c"}))); assert!(atoms_are_equivalent(&expr!(a b), &expr!(c d))); assert!(!atoms_are_equivalent(&expr!(a "b" {"c"}), &expr!(a "x" {"c"}))); diff --git a/lib/src/common/assert.rs b/lib/src/common/assert.rs index 501f5983d..a65cc17e9 100644 --- a/lib/src/common/assert.rs +++ b/lib/src/common/assert.rs @@ -1,50 +1,105 @@ -use super::collections::ListMap; +use super::collections::{ListMap, Equality, DefaultEquality}; use std::cmp::Ordering; -pub fn vec_eq_no_order<'a, T: PartialEq + std::fmt::Debug + 'a, A: Iterator, B: Iterator>(left: A, right: B) -> Result<(), String> { - let mut left_count: ListMap<&T, usize> = ListMap::new(); - let mut right_count: ListMap<&T, usize> = ListMap::new(); +pub fn vec_eq_no_order<'a, T, A, B>(left: A, right: B) -> Option +where + T: 'a + PartialEq + std::fmt::Debug, + A: Iterator, + B: Iterator, +{ + compare_vec_no_order(left, right, DefaultEquality{}).as_string() +} + +pub fn compare_vec_no_order<'a, T, A, B, E>(left: A, right: B, _cmp: E) -> VecDiff<'a, T, E> +where + A: Iterator, + B: Iterator, + E: Equality<&'a T>, +{ + let mut left_count: ListMap<&T, usize, E> = ListMap::new(); + let mut right_count: ListMap<&T, usize, E> = ListMap::new(); for i in left { *left_count.entry(&i).or_insert(0) += 1; } for i in right { *right_count.entry(&i).or_insert(0) += 1; } - counter_eq_explanation(&left_count, &right_count) + VecDiff{ left_count, right_count } } -fn counter_eq_explanation(left: &ListMap<&T, usize>, right: &ListMap<&T, usize>) -> Result<(), String> { - for e in right.iter() { - if let Some(count) = left.get(e.0) { - match count.cmp(e.1) { - Ordering::Less => return Err(format!("Missed result: {:?}", e.0)), - Ordering::Greater => return Err(format!("Excessive result: {:?}", e.0)), - Ordering::Equal => {}, +pub struct VecDiff<'a, T, E: Equality<&'a T>> { + left_count: ListMap<&'a T, usize, E>, + right_count: ListMap<&'a T, usize, E>, +} + +trait DiffVisitor<'a, T> { + fn diff(&mut self, item: &'a T, left: usize, right: usize) -> bool; +} + +impl<'a, T: std::fmt::Debug, E: Equality<&'a T>> VecDiff<'a, T, E> { + pub fn has_diff(&self) -> bool { + #[derive(Default)] + struct FindDiff { + diff: bool, + } + impl DiffVisitor<'_, T> for FindDiff { + fn diff(&mut self, _item: &T, left: usize, right: usize) -> bool { + if left == right { + false + } else { + self.diff = true; + true + } } - } else { - return Err(format!("Missed result: {:?}", e.0)); } + let mut f = FindDiff::default(); + self.visit(&mut f); + f.diff } - for e in left.iter() { - if let Some(count) = right.get(e.0) { - match e.1.cmp(count) { - Ordering::Less => return Err(format!("Missed result: {:?}", e.0)), - Ordering::Greater => return Err(format!("Excessive result: {:?}", e.0)), - Ordering::Equal => {}, + + pub fn as_string(&self) -> Option { + #[derive(Default)] + struct StringDiff { + diff: Option, + } + impl<'a, T: std::fmt::Debug> DiffVisitor<'a, T> for StringDiff { + fn diff(&mut self, item: &'a T, left: usize, right: usize) -> bool { + match left.cmp(&right) { + Ordering::Less => { + self.diff = Some(format!("Missed result: {:?}", item)); + true + }, + Ordering::Greater => { + self.diff = Some(format!("Excessive result: {:?}", item)); + true + }, + Ordering::Equal => false, + } } - } else { - return Err(format!("Excessive result: {:?}", e.0)); + } + let mut d = StringDiff{ diff: None }; + self.visit(&mut d); + d.diff + } + + fn visit<'b, V: DiffVisitor<'b, T>>(&'b self, visitor: &mut V) { + for e in self.right_count.iter() { + let count = self.left_count.get(e.0).unwrap_or(&0); + if visitor.diff(e.0, *count, *e.1) { return } + } + for e in self.left_count.iter() { + let count = self.right_count.get(e.0).unwrap_or(&0); + if visitor.diff(e.0, *e.1, *count) { return } } } - Ok(()) } #[macro_export] macro_rules! assert_eq_no_order { ($left:expr, $right:expr) => { { - assert!($crate::common::assert::vec_eq_no_order($left.iter(), $right.iter()) == Ok(()), + assert!($crate::common::assert::vec_eq_no_order($left.iter(), $right.iter()) == None, "(left == right some order)\n left: {:?}\n right: {:?}", $left, $right); } } @@ -56,7 +111,7 @@ pub fn metta_results_eq( match (left, right) { (Ok(left), Ok(right)) if left.len() == right.len() => { for (left, right) in left.iter().zip(right.iter()) { - if let Err(_) = vec_eq_no_order(left.iter(), right.iter()) { + if vec_eq_no_order(left.iter(), right.iter()).is_some() { return false; } } diff --git a/lib/src/common/collections.rs b/lib/src/common/collections.rs index b1fb834cd..720a46f93 100644 --- a/lib/src/common/collections.rs +++ b/lib/src/common/collections.rs @@ -1,16 +1,31 @@ use std::fmt::Display; +pub trait Equality { + fn eq(a: &T, b: &T) -> bool; +} + +#[derive(Debug)] +pub struct DefaultEquality {} + +impl Equality for DefaultEquality { + fn eq(a: &T, b: &T) -> bool { + a == b + } +} + #[derive(Clone, Debug)] -pub struct ListMap { +pub struct ListMap = DefaultEquality> +{ list: Vec<(K, V)>, + _phantom: std::marker::PhantomData, } -pub enum ListMapEntry<'a, K, V> { - Occupied(K, &'a mut ListMap), - Vacant(K, &'a mut ListMap), +pub enum ListMapEntry<'a, K, V, E: Equality> { + Occupied(K, &'a mut ListMap), + Vacant(K, &'a mut ListMap), } -impl<'a, K: PartialEq, V> ListMapEntry<'a, K, V> { +impl<'a, K, V, E: Equality> ListMapEntry<'a, K, V, E> { pub fn or_insert(self, default: V) -> &'a mut V { match self { ListMapEntry::Occupied(key, map) => map.get_mut(&key).unwrap(), @@ -44,7 +59,7 @@ macro_rules! list_map_get { ($get:ident, {$( $mut_:tt )?}) => { pub fn $get(& $( $mut_ )? self, key: &K) -> Option<& $( $mut_ )? V> { for (k, v) in & $( $mut_ )? self.list { - if *k == *key { + if E::eq(k, key) { return Some(v) } } @@ -53,12 +68,12 @@ macro_rules! list_map_get { } } -impl ListMap { +impl> ListMap { pub fn new() -> Self { - Self{ list: vec![] } + Self{ list: vec![], _phantom: std::marker::PhantomData } } - pub fn entry<'a>(&'a mut self, key: K) -> ListMapEntry<'a, K, V> { + pub fn entry<'a>(&'a mut self, key: K) -> ListMapEntry<'a, K, V, E> { match self.get_mut(&key) { Some(_) => ListMapEntry::Occupied(key, self), None => ListMapEntry::Vacant(key, self) diff --git a/lib/src/metta/runner/stdlib/debug.rs b/lib/src/metta/runner/stdlib/debug.rs index 5f8006784..326b04abe 100644 --- a/lib/src/metta/runner/stdlib/debug.rs +++ b/lib/src/metta/runner/stdlib/debug.rs @@ -2,19 +2,20 @@ use crate::*; use crate::metta::*; use crate::metta::text::Tokenizer; use crate::space::*; -use crate::common::assert::vec_eq_no_order; - +use crate::common::collections::Equality; +use crate::common::assert::{vec_eq_no_order, compare_vec_no_order}; +use crate::atom::matcher::atoms_are_equivalent; use crate::metta::runner::stdlib::{grounded_op, atom_to_string, regex, interpret_no_error, unit_result}; +use crate::metta::runner::bool::*; use std::convert::TryInto; -fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) -> Result, ExecError> { - log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom); +fn assert_results_equal(actual: &Vec, expected: &Vec) -> Result, ExecError> { let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); match vec_eq_no_order(actual.iter(), expected.iter()) { - Ok(()) => unit_result(), - Err(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) + None => unit_result(), + Some(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) } } @@ -104,6 +105,23 @@ impl CustomExecute for PrintAlternativesOp { } } +struct AlphaEquality{} + +impl Equality<&Atom> for AlphaEquality { + fn eq(a: &&Atom, b: &&Atom) -> bool { + atoms_are_equivalent(*a, *b) + } +} + +fn assert_alpha_equal(actual: &Vec, expected: &Vec) -> Result, ExecError> { + let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); + let res = compare_vec_no_order(actual.iter(), expected.iter(), AlphaEquality{}); + match res.as_string() { + None => unit_result(), + Some(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) + } +} + #[derive(Clone, Debug)] pub struct AssertEqualOp { space: DynSpace, @@ -119,7 +137,7 @@ impl AssertEqualOp { impl Grounded for AssertEqualOp { fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE]) } fn as_execute(&self) -> Option<&dyn CustomExecute> { @@ -130,14 +148,77 @@ impl Grounded for AssertEqualOp { impl CustomExecute for AssertEqualOp { fn execute(&self, args: &[Atom]) -> Result, ExecError> { log::debug!("AssertEqualOp::execute: {:?}", args); - let arg_error = || ExecError::from("assertEqual expects two atoms as arguments: actual and expected"); + let arg_error = || ExecError::from("assertEqual expects two atoms: actual and expected"); let actual_atom = args.get(0).ok_or_else(arg_error)?; let expected_atom = args.get(1).ok_or_else(arg_error)?; let actual = interpret_no_error(self.space.clone(), actual_atom)?; let expected = interpret_no_error(self.space.clone(), expected_atom)?; - assert_results_equal(&actual, &expected, actual_atom) + assert_results_equal(&actual, &expected) + } +} + +#[derive(Clone, Debug)] +pub struct AssertAlphaEqualOp { + space: DynSpace, +} + +grounded_op!(AssertAlphaEqualOp, "assertAlphaEqual"); + +impl AssertAlphaEqualOp { + pub fn new(space: DynSpace) -> Self { + Self{ space } + } +} + +impl Grounded for AssertAlphaEqualOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for AssertAlphaEqualOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + log::debug!("AssertAlphaEqualOp::execute: {:?}", args); + let arg_error = || ExecError::from("assertAlphaEqual expects two atoms: actual and expected"); + let actual_atom = args.get(0).ok_or_else(arg_error)?; + let expected_atom = args.get(1).ok_or_else(arg_error)?; + + let actual = interpret_no_error(self.space.clone(), actual_atom)?; + let expected = interpret_no_error(self.space.clone(), expected_atom)?; + + assert_alpha_equal(&actual, &expected) + } +} + +#[derive(Clone, Debug)] +pub struct AlphaEqOp { +} +grounded_op!(AlphaEqOp, "=alpha"); + +impl Grounded for AlphaEqOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_BOOL]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for AlphaEqOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + log::debug!("AlphaEqOp::execute: {:?}", args); + let arg_error = || ExecError::from("=alpha expects two atoms as arguments: actual and expected"); + let actual_atom = args.get(0).ok_or_else(arg_error)?; + let expected_atom = args.get(1).ok_or_else(arg_error)?; + + Ok(vec![Atom::gnd(Bool(atoms_are_equivalent(actual_atom, expected_atom)))]) } } @@ -156,7 +237,7 @@ impl AssertEqualToResultOp { impl Grounded for AssertEqualToResultOp { fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, UNIT_TYPE]) } fn as_execute(&self) -> Option<&dyn CustomExecute> { @@ -167,7 +248,45 @@ impl Grounded for AssertEqualToResultOp { impl CustomExecute for AssertEqualToResultOp { fn execute(&self, args: &[Atom]) -> Result, ExecError> { log::debug!("AssertEqualToResultOp::execute: {:?}", args); - let arg_error = || ExecError::from("assertEqualToResult expects two atoms as arguments: actual and expected"); + let arg_error = || ExecError::from("assertEqualToResult expects atom and expression as arguments: actual and expected"); + let actual_atom = args.get(0).ok_or_else(arg_error)?; + let expected = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?) + .map_err(|_| arg_error())? + .children(); + + let actual = interpret_no_error(self.space.clone(), actual_atom)?; + + assert_results_equal(&actual, &expected.into()) + } +} + +#[derive(Clone, Debug)] +pub struct AssertAlphaEqualToResultOp { + space: DynSpace, +} + +grounded_op!(AssertAlphaEqualToResultOp, "assertAlphaEqualToResult"); + +impl AssertAlphaEqualToResultOp { + pub fn new(space: DynSpace) -> Self { + Self{ space } + } +} + +impl Grounded for AssertAlphaEqualToResultOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, UNIT_TYPE]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for AssertAlphaEqualToResultOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + log::debug!("AssertAlphaEqualToResultOp::execute: {:?}", args); + let arg_error = || ExecError::from("assertAlphaEqualToResultOp expects atom and expression as arguments: actual and expected"); let actual_atom = args.get(0).ok_or_else(arg_error)?; let expected = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?) .map_err(|_| arg_error())? @@ -175,10 +294,11 @@ impl CustomExecute for AssertEqualToResultOp { let actual = interpret_no_error(self.space.clone(), actual_atom)?; - assert_results_equal(&actual, &expected.into(), actual_atom) + assert_alpha_equal(&actual, &expected.into()) } } + pub fn register_common_tokens(tref: &mut Tokenizer) { let trace_op = Atom::gnd(TraceOp{}); tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() }); @@ -187,10 +307,20 @@ pub fn register_common_tokens(tref: &mut Tokenizer) { } pub fn register_runner_tokens(tref: &mut Tokenizer, space: &DynSpace) { - let assert_equal_op = Atom::gnd(AssertEqualOp::new(space.clone())); - tref.register_token(regex(r"assertEqual"), move |_| { assert_equal_op.clone() }); + let assert_alpha_equal_to_result_op = Atom::gnd(AssertAlphaEqualToResultOp::new(space.clone())); + tref.register_token(regex(r"assertAlphaEqualToResult"), move |_| { assert_alpha_equal_to_result_op.clone() }); + let assert_equal_to_result_op = Atom::gnd(AssertEqualToResultOp::new(space.clone())); tref.register_token(regex(r"assertEqualToResult"), move |_| { assert_equal_to_result_op.clone() }); + + let assert_alpha_equal_op = Atom::gnd(AssertAlphaEqualOp::new(space.clone())); + tref.register_token(regex(r"assertAlphaEqual"), move |_| { assert_alpha_equal_op.clone() }); + + let assert_equal_op = Atom::gnd(AssertEqualOp::new(space.clone())); + tref.register_token(regex(r"assertEqual"), move |_| { assert_equal_op.clone() }); + + let alpha_eq_op = Atom::gnd(AlphaEqOp{}); + tref.register_token(regex(r"=alpha"), move |_| { alpha_eq_op.clone() }); } #[cfg(test)] @@ -198,6 +328,7 @@ mod tests { use super::*; use crate::metta::runner::{Metta, EnvBuilder, SExprParser}; use crate::common::test_utils::metta_space; + use crate::metta::runner::stdlib::tests::run_program; use regex::Regex; @@ -229,6 +360,32 @@ mod tests { ])); } + #[test] + fn metta_assert_alpha_equal_op() { + let metta = Metta::new(Some(EnvBuilder::test_env())); + let assert = AssertAlphaEqualOp::new(metta.space().clone()); + let program = " + (= (foo $x) $x) + (= (bar $x) $x) + "; + assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo $x) (bar $x))")), Ok(vec![ + vec![UNIT_ATOM], + ])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo A) (bar B))")), Ok(vec![ + vec![expr!("Error" ({assert.clone()} ("foo" "A") ("bar" "B")) "\nExpected: [B]\nGot: [A]\nMissed result: B")], + ])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo A) Empty)")), Ok(vec![ + vec![expr!("Error" ({assert.clone()} ("foo" "A") "Empty") "\nExpected: []\nGot: [A]\nExcessive result: A")] + ])); + } + + #[test] + fn metta_alpha_eq_op() { + assert_eq!(run_program(&format!("(= (foo) (R $x $y)) !(let $foo (eval (foo)) (=alpha $foo (R $x $y)))")), Ok(vec![vec![expr!({Bool(true)})]])); + assert_eq!(run_program(&format!("(= (foo) (R $x $y)) !(let $foo (eval (foo)) (=alpha $foo (R $x $x)))")), Ok(vec![vec![expr!({Bool(false)})]])); + } + #[test] fn metta_assert_equal_to_result_op() { let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -252,6 +409,37 @@ mod tests { ])); } + #[test] + fn metta_assert_alpha_equal_to_result_op() { + let metta = Metta::new(Some(EnvBuilder::test_env())); + let assert = AssertAlphaEqualToResultOp::new(metta.space().clone()); + let program = " + (= (foo) $x) + (= (bar) C) + (= (baz) D) + (= (baz) D) + "; + assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (foo) ($x))")), Ok(vec![ + vec![UNIT_ATOM], + ])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult ((foo) (foo)) (($x $y)))")), Ok(vec![ + vec![UNIT_ATOM], + ])); + + let res = metta.run(SExprParser::new("!(assertAlphaEqualToResult ((foo) (foo)) (($x $x)))")).unwrap(); + let res_first_atom = res.get(0).unwrap().get(0); + assert_eq!(res_first_atom.unwrap().iter().next().unwrap(), &sym!("Error")); + assert_eq!(res.get(0).unwrap().len(), 1); + + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (bar) (A))")), Ok(vec![ + vec![expr!("Error" ({assert.clone()} ("bar") ("A")) "\nExpected: [A]\nGot: [C]\nMissed result: A")], + ])); + assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (baz) (D))")), Ok(vec![ + vec![expr!("Error" ({assert.clone()} ("baz") ("D")) "\nExpected: [D]\nGot: [D, D]\nExcessive result: D")] + ])); + } + #[test] fn assert_equal_op() { let space = DynSpace::new(metta_space(" @@ -293,4 +481,4 @@ mod tests { assert_eq!(TraceOp{}.execute(&mut vec![sym!("\"Here?\""), sym!("42")]), Ok(vec![sym!("42")])); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/stdlib.metta b/lib/src/metta/runner/stdlib/stdlib.metta index aa364322a..58da2572e 100644 --- a/lib/src/metta/runner/stdlib/stdlib.metta +++ b/lib/src/metta/runner/stdlib/stdlib.metta @@ -984,19 +984,40 @@ (@params ()) (@return "Unit atom")) +(@doc =alpha + (@desc "Checks alpha equality of two expressions") + (@params ( + (@param "First expression") + (@param "Second expression"))) + (@return "True if both expressions are alpha equal, False - otherwise")) + (@doc assertEqual (@desc "Compares (sets of) results of evaluation of two expressions") (@params ( (@param "First expression") (@param "Second expression"))) - (@return "Unit atom if both expression after evaluation is equal, error - otherwise")) + (@return "Unit atom if both expressions after evaluation are equal, error - otherwise")) + +(@doc assertAlphaEqual + (@desc "Compares (sets of) results of evaluation of two expressions using alpha equality") + (@params ( + (@param "First expression") + (@param "Second expression"))) + (@return "Unit atom if both expressions after evaluation are alpha equal, error - otherwise")) (@doc assertEqualToResult (@desc "Same as assertEqual but it doesn't evaluate second argument. Second argument is considered as a set of values of the first argument's evaluation") (@params ( (@param "First expression (it will be evaluated)") (@param "Second expression (it won't be evaluated)"))) - (@return "Unit atom if both expression after evaluation is equal, error - otherwise")) + (@return "Unit atom if both expressions after evaluation of the first argument are equal, error - otherwise")) + +(@doc assertAlphaEqualToResult + (@desc "Same as assertAlphaEqual but it doesn't evaluate second argument. Second argument is considered as a set of values of the first argument's evaluation") + (@params ( + (@param "First expression (it will be evaluated)") + (@param "Second expression (it won't be evaluated)"))) + (@return "Unit atom if both expressions after evaluation of the first argument are alpha equal, error - otherwise")) (@doc superpose (@desc "Turns a tuple (first argument) into a nondeterministic result")