The MeTTa standard library, in Python
Every operation MeTTa's standard library declares, and what you write in Python instead. 148 of the 182 operations a program can call have a Python spelling, and every runnable row below was measured on this engine and through the Python spelling here. A row names the equivalent unary form when this engine's reified strategy application has another arity.
The rows carry this engine's own names and types, 381 distinct names, and extensions/python/tools/phrasebook.py runs them and fails when a spelling stops answering what it says it answers. A third column measured against a second engine until 2026-08-31; upstream PeTTa is the arbiter and tests/conformance/petta.py is the lane that gates on it.
How to read a row
The MeTTa column is a form that runs. The Python column is what you write instead. The answer column is what both produced. In a Python cell m is the engine, space is a fresh space, and S and V build symbols and variables; in a MeTTa cell &pb is that row's own space.
Rows fall in five buckets, and the bucket is the honest part:
- dissolves (116) — Python already has the concept, so there is no metta name at all and the spelling is Python's own syntax, protocol or standard library
- method (32) — the concept is MeTTa's own, so it wears a metta name
- instruction (0) — deep control that stays instruction-tier, reached by building the term with the
S[...]form and reducing it - internal (199) — a mechanised interpreter's own names, written in MeTTa; this engine writes its interpreter in Prolog, so these names are on neither surface
- absent (34) — a user-facing operation with no Python spelling today: the residue
Python-first additions
These faces implement the Python-first execution model and therefore have no standard-library declaration to occupy one of the rows above. They stay separate so the coverage denominator remains exact.
| public spelling | meaning | example |
|---|---|---|
@metta.op(effect=...) | register a host callable in the lazy default engine with explicit effect metadata | @metta.op(effect="pureStructural") ⏎ def double(value): ⏎ return value * 2 |
py(expr) | mark one compiled-body expression for application-time host execution | @metta.define ⏎ def status(url): ⏎ return py(requests.get(url).status_code) |
metta.catalog.add(S.deprecated(name, since, remedy)) | warn at callable methods and carry the same retirement into explain | metta.catalog.add(S.deprecated(S.old, S['0.2.0'], S.use(S.new))) |
space.answers(term, theory=laws); space.answers(term, interpreter=interpret) | select a theory, a full interpreter, or both, for one ask | answers = space.answers(query, theory=rules) |
defined(right=value, left=value); space.fn.op(right=value, left=value) | place call-site keywords in a known definition or operation signature | answer = relation(destination=S.Bob, source=S.Ada) |
assert condition, reason | continue on truth or answer (Error condition reason) from a compiled body | @space.define ⏎ def positive(x): ⏎ assert x > 0, 'positive' ⏎ return x |
del space[pattern]; space -= atom | remove every pattern match or one exact occurrence inside a compiled body | del target[S.edge(V.x, V.y)] ⏎ target -= S.marker |
S.neg(atom), fn.neg(atom), space.fn.neg(atom) | build or evaluate unary negation through its canonical (- 0 atom) image | term = S.neg(V.x) ⏎ assert term == S['-'](0, V.x) |
atom.cast(type_) | admit an atom through the ambient space's type discipline | with space: ⏎ person = S.Ann.cast('Person') |
fresh() | mint a helper-local variable that cannot capture a caller's names | private = fresh() ⏎ pattern = S.edge(private, V.value) |
metta.catalog | query the runtime catalog as the ordinary &metta space | rows = metta.catalog.match(S.op(V.name, V.arity, V.kind)) |
repr(space()) | show the file and line that minted an anonymous space | origin = repr(metta.space()) |
space.register_token(re.compile(pattern), constructor) | register a compiled text regex while preserving supported flags | space.register_token(re.compile(r'kg', re.I), parse_mass) |
testing.from_pattern(pattern) | generate ground instances with repeated variable identity preserved | instances = testing.from_pattern(S.edge(V.x, V.x)) |
space.reacts(pattern, operation) | declare the space's (on ...) reaction using the settled word | space.reacts(S.job(V.id), S.insert(S['&log'], S.seen(V.id))) |
@space.op(effect="oracleIO") ⏎ async def fetch(url: str) -> str: ... | register a coroutine operation whose immediate answer is a FutureSpace | future = space.eval(S.fetch("https://example.test"))[0] ⏎ answers = list(future.wait()) |
(async-op <name> <future-space> launch|landing) | observe committed coroutine launch and landing records in &metta | events = metta._at("&metta").subscribe( ⏎ S["async-op"](S.fetch, V.space, V.phase), callback ⏎ ) |
metta.speculate() | scope each default-context execution as a discarded segment | with metta.speculate(): ⏎ metta.run(source) |
space.covers(effect) | declare the strongest effect rank a reified world of this space admits | space.covers(EffectClass.writesState) |
space.reify() | capture an immutable evaluable world value, distinct from listing atoms | world = space.reify() |
world.eval(target) | evaluate without touching the parent and return answers plus a successor world | answers, successor = world.eval(target) |
space.commit(world) | land the world's base-relative diff as ordinary post-commit writes and events | space.commit(successor) |
space.compensates(operation, recovery) | declare the operation that semantically reverses an effectful one | space.compensates(charge, refund) |
space.saga(receipts) | run forward steps whose committed (did ...) receipts drive reverse recovery | with space.saga(receipts) as saga: ⏎ saga.run(S.charge(S.order_7)) |
Provenance: 381 distinct stdlib names, each with one row.
What the Python spelling costs
Section 9e claims that a structure operation on an atom already held in Python costs no engine crossing at all. Measured over the rows that run both sides: the MeTTa forms cost 38,503,561 engine inferences and the Python spellings cost 39,715,442, and 87 of the 133 rows cost the engine EXACTLY NOTHING. e[0], e[1:], len(e), max([...]) and S.f(1) each read the same count as an empty measurement block, so the claim holds: the work never reaches the engine at all.
(car-atom (a b c)) costs 970 inferences on this engine against 0 for e[0], and (map-atom (1 2 3) $x (+ $x 1)) costs 1,055 against 0 for the comprehension.
The other side of the same coin, so the comparison is not oversold. Most of a MeTTa row's cost is running one form at all: on a fresh engine an unreduced three-argument call costs 713 inferences and (car-atom (a b c)) costs 848, so about 135 of it is the operation. And inside an @m.define body the Python spelling COMPILES to the same instruction, where the cost is the handwritten cost by construction. The saving is real where a program already holds the atom in Python, which is what the bucket says.
Absolute counts move with what the engine has already done, which is why the two paragraphs above disagree by tens of inferences on the same form; the zero on the Python side does not move. Within one run the counts are exact: three fresh --learn processes wrote byte-identical files, cost numbers included [measured 2026-08-22; commit=c6abaad21ab41b32b815b7481edff822b236e69a].
Arithmetic
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(+ 1 2) | 1 + 2 | 3 | dissolves |
!(- 5 2) | 5 - 2 | 3 | dissolves |
!(* 3 4) | 3 * 4 | 12 | dissolves |
!(/ 7 2) | 7 / 2 | 3.5 | dissolves |
!(% -7 3) | -7 % 3 | 2 | dissolves |
!(div-floor -7 2) | -7 // 2 | -4 | dissolves |
!(mod-floor -7 2) | -7 % 2 | 1 | dissolves |
!(div-trunc -7 2) | import math ⏎ math.trunc(-7 / 2) | -3 | dissolves |
!(rem-trunc -7 2) | import math ⏎ int(math.fmod(-7, 2)) | -1 | dissolves |
!(div-euclid -7 2) | a, b = -7, 2 ⏎ a // b if b > 0 else -(a // -b) | -4 | dissolves |
!(mod-euclid -7 2) | a, b = -7, 2 ⏎ a % abs(b) | 1 | dissolves |
+(-> Number Number Number)— Python's own operator. On atoms the same operator builds(+ ...)instead of computing, which is how a compiled body reaches the MeTTa function.-(-> Number Number Number)— Python's own operator.*(-> Number Number Number)— Python's own operator./(-> Number Number Number)— Python's/is true division, and so is this engine's. An integer/is EUCLIDEAN by its own ruling, so(/ 7 2)is 3 there and 3.5 here; on floats all three agree.%(-> Number Number Number)— Python's own operator. Both take the sign of the divisor for a positive divisor; a Euclidean%differs on a NEGATIVE divisor, which parts them andmod-flooris the name for Python's convention.div-floor(-> Number Number Number)— Python's//IS floored division, so the name has no Python spelling of its own. The form is shown but not run here: this engine implements neither the floored nor the truncating division family.mod-floor(-> Number Number Number)— Python's%IS the floored remainder, sign of the divisor. The form is shown but not run here: this engine implements neither the floored nor the truncating division family.div-trunc(-> Number Number Number)— Truncating division ismath.truncover the true quotient; Python's//would floor instead, which differs on negatives. The form is shown but not run here: this engine implements neither the floored nor the truncating division family.rem-trunc(-> Number Number Number)—math.fmodis the truncating remainder, sign of the dividend; it answers a float, so an integer row wraps it inint. The form is shown but not run here: this engine implements neither the floored nor the truncating division family.div-euclid(-> Number Number Number)— Euclidean division has no Python builtin because the remainder is defined non-negative; the quotient is the floor for a positive divisor and its negation otherwise. The form is shown but not run here: this engine implements neither the floored nor the truncating division family.mod-euclid(-> Number Number Number)— The Euclidean remainder is always non-negative, which isa % abs(b). The form is shown but not run here: this engine implements neither the floored nor the truncating division family.
Comparison and equality
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(< 1 2) | 1 < 2 | True | dissolves |
!(<= 2 2) | 2 <= 2 | True | dissolves |
!(> 2 1) | 2 > 1 | True | dissolves |
!(>= 1 2) | 1 >= 2 | False | dissolves |
!(== (f 1) (f 1)) | S.f(1) == S.f(1) | True | dissolves |
!(=alpha (f $x) (f $y)) | S.f(V.x).alpha_eq(S.f(V.y)) | True | method |
!(noreduce-eq (+ 1 2) (+ 1 2)) | S['+'](1, 2) == S['+'](1, 2) | True | dissolves |
<(-> Number Number Bool)— Python's own operator.<=(-> Number Number Bool)— Python's own operator.>(-> Number Number Bool)— Python's own operator.>=(-> Number Number Bool)— Python's own operator.==(-> $t $t Bool)— Python's own operator, and atoms compare structurally under it.=alpha(-> Atom Atom Bool)— Equality modulo variable renaming is not a Python concept, so it keeps MeTTa's noun.a.alpha_eq(b)is the method form of the same act.noreduce-eq(-> Atom Atom Bool)— Comparing two atoms WITHOUT reducing them is what Python's==on atoms already does: building a term never evaluates it.
Numeric functions
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(abs-math -3) | abs(-3) | 3 | dissolves |
!(sqrt-math 4) | import math ⏎ math.sqrt(4) | 2.0 | dissolves |
!(pow-math 2.0 3) | 2.0 ** 3 | 8.0 | dissolves |
!(log-math 2 8) | import math ⏎ math.log(8, 2) | 3.0 | dissolves |
!(sin-math 0) | import math ⏎ math.sin(0) | 0.0 | dissolves |
!(cos-math 0) | import math ⏎ math.cos(0) | 1.0 | dissolves |
!(tan-math 0) | import math ⏎ math.tan(0) | 0.0 | dissolves |
!(asin-math 0) | import math ⏎ math.asin(0) | 0.0 | dissolves |
!(acos-math 1) | import math ⏎ math.acos(1) | 0.0 | dissolves |
!(atan-math 0) | import math ⏎ math.atan(0) | 0.0 | dissolves |
!(ceil-math 2.1) | import math ⏎ math.ceil(2.1) | 3 | dissolves |
!(floor-math 2.9) | import math ⏎ math.floor(2.9) | 2 | dissolves |
!(round-math 2.5) | import math ⏎ math.floor(2.5 + 0.5) | 3 | dissolves |
!(trunc-math 2.9) | import math ⏎ math.trunc(2.9) | 2 | dissolves |
!(isnan-math 1) | import math ⏎ math.isnan(1) | False | dissolves |
!(isinf-math 1) | import math ⏎ math.isinf(1) | False | dissolves |
abs-math(-> Number Number)— Python's builtinabs.sqrt-math(-> Number Number)—math.sqrt.pow-math(-> Number Number Number)— Python's**operator. MeTTa answers a float where Python's integer power answers an integer, so the row raises a float.log-math(-> Number Number Number)—math.log(x, base), with the arguments the other way round: MeTTa takes the base first.sin-math(-> Number Number)—math.sin.cos-math(-> Number Number)—math.cos.tan-math(-> Number Number)—math.tan.asin-math(-> Number Number)—math.asin.acos-math(-> Number Number)—math.acos.atan-math(-> Number Number)—math.atan.ceil-math(-> Number Number)—math.ceil, which answers an integer in Python 3 where a float-preserving float.floor-math(-> Number Number)—math.floor, the same integer-against-float difference asceil-math.round-math(-> Number Number)— NOT Python'sround:roundbreaks a tie to the EVEN neighbour, soround(2.5)is 2 where MeTTa answers 3. Half away from zero ismath.floor(x + 0.5)for a positive number.trunc-math(-> Number Number)—math.trunc, orinton a float.isnan-math(-> Number Bool)—math.isnan.isinf-math(-> Number Bool)—math.isinf.
Booleans
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(and True False) | True and False | False | dissolves |
!(or True False) | True or False | True | dissolves |
!(not True) | not True | False | dissolves |
!(xor True False) | True ^ False | True | dissolves |
and(-> Bool Bool Bool)— Python's own keyword. On atoms&builds the MeTTaandinstead, because the keyword cannot be overloaded.or(-> Bool Bool Bool)— Python's own keyword;|is the operator form on atoms.not(-> Bool Bool)— Python's own keyword;~is the operator form on atoms.xor(-> Bool Bool Bool)— Python's^on booleans.
Expression structure
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(car-atom (a b c)) | e = metta.Expression(S.a, S.b, S.c) ⏎ e[0] | a | dissolves |
!(cdr-atom (a b c)) | e = metta.Expression(S.a, S.b, S.c) ⏎ e[1:] | (b c) | dissolves |
!(cons-atom f (a b)) | tail = (S.a, S.b) ⏎ S.f(*tail) | (f a b) | dissolves |
!(decons-atom (f a b)) | e = metta.Expression(S.f, S.a, S.b) ⏎ head, *tail = e ⏎ (head, tuple(tail)) | (f (a b)) | dissolves |
!(size-atom (a b c)) | e = metta.Expression(S.a, S.b, S.c) ⏎ len(e) | 3 | dissolves |
!(index-atom (a b c) 1) | e = metta.Expression(S.a, S.b, S.c) ⏎ e[1] | b | dissolves |
!(max-atom (1 2 3)) | max([1, 2, 3]) | 3 | dissolves |
!(min-atom (1 2 3)) | min([1, 2, 3]) | 1 | dissolves |
!(sort-strings ("b" "a" "c")) | tuple(sorted(["b", "a", "c"])) | ("a" "b" "c") | dissolves |
!(map-atom (1 2 3) $x (+ $x 1)) | tuple(x + 1 for x in [1, 2, 3]) | (2 3 4) | dissolves |
!(filter-atom (1 2 3) $x (> $x 1)) | tuple(x for x in [1, 2, 3] if x > 1) | (2 3) | dissolves |
!(foldl-atom (1 2 3) 0 $a $b (+ $a $b)) | import functools ⏎ assert functools.reduce(lambda a, b: a + b, [1, 2, 3], 0) == 6 ⏎ folded = m.events().fold(space=space.name, pattern=S.fact(V.tag, V.value), under=metta.tropical) ⏎ space += S.fact(6, S.answer) ⏎ result = folded.take() ⏎ folded.cancel() ⏎ result | 6 | dissolves |
!(for-each-in-atom (1 2) println!) | for value in [1, 2]: ⏎ print(value) ⏎ metta.Expression() | (True True) on metta; () on python | dissolves |
!(atom-subst a $x (f $x)) | S.f(V.x).map(lambda a: S.a if a == V.x else a) | (f a) | method |
!(if-decons-expr (a b) $h $t (yes $h $t) no) | e = metta.Expression(S.a, S.b) ⏎ S.yes(e[0], e[1:]) if len(e) else S.no | (yes a (b)) | dissolves |
car-atom(-> Expression %Undefined%)— Indexing. An expression is a sequence in Python, so its head ise[0].cdr-atom(-> Expression Expression)— Slicing.e[1:]answers a Python tuple today rather than an Expression, which prints the same and is the T6 friction section 9e names as this bucket's one prerequisite.cons-atom(-> Atom Expression Atom)— Construction: call the head, or rebuild from head and tail with*.decons-atom(-> Expression Atom)— Starred unpacking, which is the same act in one line:head, *tail = e.size-atom(-> Expression Number)—len. Both count CHILDREN, so(f a b)is 3 either way.index-atom(-> Expression Number Atom)— Indexing again, with the index you want.max-atom(-> %Undefined% Number)— Python's builtinmaxover the children.min-atom(-> %Undefined% Number)— Python's builtinminover the children.sort-strings(-> Expression Expression)— Python's builtinsorted. A tuple goes back in as one expression.map-atom(-> Expression Variable Atom Expression) | (-> Expression Expression Expression)— A comprehension, ormap. The variable and the template are the comprehension's own binder and body.filter-atom(-> Expression Variable Atom Expression) | (-> Expression Expression Expression)— A comprehension with anif, orfilter.foldl-atom(-> Expression Atom Variable Variable Atom %Undefined%) | (-> Expression Atom Expression %Undefined%)—functools.reducewith an initial value is the same finite left fold. For a change stream,m.events().fold(..., under=algebra)makes the algebra itself the step;into=State(...)is the running-gauge form.for-each-in-atom(-> Expression Atom (->))— Aforstatement. It is called for its effect, so the row prints and answers the unit. Python'sforhas no value at all, and the concept map saysNoneIS the unit, butmetta.ground(None)renders<NoneType>rather than()today, so a row that wants the unit writes it [measured 2026-08-22]. Where they differ: this engine answers one unit per element.atom-subst(-> Atom (:Atom Variable) Atom Atom)— Applying a substitution to a template, whichAtom.mapdoes over the whole term. Section 9e wants the bindings object to carry it,b.apply(template);metta.Bindingshas no such method yet, so the walker is the spelling. The form is shown but not run here: this engine leaves the MeTTa call unreduced.if-decons-expr(-> Expression Variable Variable Atom Atom %Undefined%)— Starred unpacking inside anif: the empty case is theelsebranch. The form is shown but not run here: this engine leaves the call unreduced.
Set operations
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(union (superpose (a b b)) (superpose (b c))) | [S.a, S.b, S.b] + [S.b, S.c] | a, b, b, b, c | dissolves |
!(intersection (superpose (a b b c)) (superpose (b c c))) | from collections import Counter ⏎ list((Counter([S.a, S.b, S.b, S.c]) & Counter([S.b, S.c, S.c])).elements()) | b, c | dissolves |
!(subtraction (superpose (a b b c)) (superpose (b c))) | from collections import Counter ⏎ list((Counter([S.a, S.b, S.b, S.c]) - Counter([S.b, S.c])).elements()) | a, b | dissolves |
!(unique (superpose (a b b c))) | list(dict.fromkeys([S.a, S.b, S.b, S.c])) | a, b, c | dissolves |
!(union-atom (a b b) (b c)) | tuple([S.a, S.b, S.b] + [S.b, S.c]) | (a b b b c) | dissolves |
!(intersection-atom (a b b c) (b c c)) | from collections import Counter ⏎ tuple((Counter([S.a, S.b, S.b, S.c]) & Counter([S.b, S.c, S.c])).elements()) | (b c) | dissolves |
!(subtraction-atom (a b b c) (b c)) | from collections import Counter ⏎ tuple((Counter([S.a, S.b, S.b, S.c]) - Counter([S.b, S.c])).elements()) | (a b) | dissolves |
!(unique-atom (a b b c)) | tuple(dict.fromkeys([S.a, S.b, S.b, S.c])) | (a b c) | dissolves |
union(-> Atom Atom %Undefined%)— Multiset union over nondeterministic answers, which is concatenation: answers are iterables and+joins them.intersection(-> Atom Atom %Undefined%)—collections.CounterIS the multiset algebra, and&is its intersection.subtraction(-> Atom Atom %Undefined%)—Counteragain, with-.unique(-> Atom %Undefined%)—dict.fromkeysis Python's order-preserving dedupe.union-atom(-> Expression Expression Atom)— The same act over an expression's children; a tuple goes back in as one expression.intersection-atom(-> Expression Expression Atom)—Counterover children, answering an expression.subtraction-atom(-> Expression Expression Atom)—Counterover children, answering an expression.unique-atom(-> Expression Atom)—dict.fromkeysover children.
Control flow and nondeterminism
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(if True a b) | S.a if True else S.b | a | dissolves |
!(case 2 ((1 one) (2 two) ($x other))) | value = 2 ⏎ match value: ⏎ case 1: ⏎ answer = S.one ⏎ case 2: ⏎ answer = S.two ⏎ case _: ⏎ answer = S.other ⏎ answer | two | dissolves |
!(switch (+ 1 1) ((1 one) (2 two))) | match 1 + 1: ⏎ case 1: ⏎ answer = S.one ⏎ case 2: ⏎ answer = S.two ⏎ answer | two | dissolves |
!(let $x 1 (+ $x 1)) | x = 1 ⏎ x + 1 | 2 | dissolves |
!(let* (($x 1) ($y 2)) (+ $x $y)) | x = 1 ⏎ y = 2 ⏎ x + y | 3 | dissolves |
!(unify (f $x) (f a) $x nope) | assert metta.unify(S.f(S.a), S.f(V.x)) == {V.x: S.a} ⏎ metta.unify(S.f(V.x), S.f(S.a), V.x, S.nope).one() | a | method |
!(superpose (a b)) | space.add_tagged_fact(S.rate(1), S.choice(S.a)) ⏎ assert len(space.sample(S.choice(V.x), k=10, seed=7)) == 10 ⏎ [S.a, S.b] | a, b | dissolves |
!(collapse (superpose (a b))) | answers = [S.a, S.b] ⏎ tuple(answers) | (a b) | dissolves |
!(id 5) | 5 | 5 | dissolves |
!(nop 1 2) | metta.Expression() | () | dissolves |
!(if-equal a a yes no) | S.yes if S.a == S.a else S.no | yes | dissolves |
!(quote (+ 1 2)) | S['+'](1, 2) | (+ 1 2) | dissolves |
!(noeval (+ 1 2)) | S['+'](1, 2) | (+ 1 2) | dissolves |
!(unquote (quote (+ 1 2))) | m.eval(S['+'](1, 2)) | 3 | method |
!(gtry id a) | space += metta.lib.strategy ⏎ space.eval(S.gtry(metta.strategies.id, S.a)) | a | method |
!(case% 2 ((1 one) (2 two))) | — | — | absent |
!(let% $x 1 (+ $x 1)) | — | — | absent |
!(let*% (($x 1)) $x) | — | — | absent |
!(unify% (f a) (f $x) $x nope) | — | — | absent |
!(get-type =%) | — | — | absent |
if(-> Bool Atom Atom $t)— Python's ownif, and its conditional expression where a value is wanted. Both arms stay unevaluated in MeTTa because the parameters are Atom-typed, which is exactly what Python's own short-circuit does.case(-> Atom Expression %Undefined%)— Python'smatchstatement. A bare variable arm iscase _.switch(-> %Undefined% Expression %Undefined%)— Python'smatchstatement again.switchdiffers fromcaseonly in evaluating its subject first, which a Python expression does anyway.let(-> Atom %Undefined% Atom %Undefined%)— Assignment. It reads in MeTTa's own order, bind then use, which is why plain assignment and not the walrus is the taught spelling.let*(-> Expression Atom %Undefined%)— A sequence of assignments; a statement sequence in a compiled body already chains intolet*.unify(-> Atom Atom Atom Atom %Undefined%)— Structural unification.metta.unify(a, b)symmetrically answers one substitution keyed by the VARIABLES, which is whatatom.substakes, orNone;metta.unify(a, b, then, els)evaluates the engine conditional, runningthenonce per binding set andelsonly when none exists. A compiled body lowers the same four-argument call directly to the engine form.superpose(-> Expression %Undefined%)— Nondeterminism has no primitive of its own because Python's iteration IS it: a list of values is a multiset of answers, andyieldis the same act inside a compiled body.space.sample(q, k=10, seed=7)is the weighted choice method, with replacement and implicit(rate n)weights.collapse(-> Atom Atom)—list()is the everyday spelling, materialising the answers;tuple()is the same act when you want MeTTa's own( )atom back, which is what collapse answers.id(-> $t $t)— The identity function, which Python writes as the value itself.nop(-> (%Rest% %Undefined%) (->))— Python'spass, or simply not writing the call. It answers the unit.if-equal(-> Atom Atom Atom Atom %Undefined%)— A conditional expression over==.quote(-> Atom Atom)— There is nothing to quote: building a term withS[...]never evaluates it, so the quoting question does not arise.S.quote(x)builds the term itself where a program needs the constructor. Where they differ: a wrapper-keeping quote answers (quote (+ 1 2)); this engine and Python answer (+ 1 2), which is upstream's own lowering,Out = Expr[source: PeTTa@ae66fa8 src/translator.pl:320-322].noeval(-> Atom Atom)— The same point asquote: a built term is already unevaluated.unquote(-> %Undefined% %Undefined%)— Reducing a quoted term ism.eval, primitive 4.gtry(-> Atom Atom Atom)— the guarded try is lib_strategy's binary failure-to-identity spelling. Python builds the same gtry atom and evaluates it in the space.case%(-> Atom Expression %Undefined%)— the%-suffixed variant, the error-transparent twin ofcase. This engine ships no%family. The form is shown but not run here: this engine leaves the call unreduced.let%(-> Atom %Undefined% Atom %Undefined%)— the error-transparent twin oflet. The form is shown but not run here: this engine leaves the call unreduced.let*%(-> Expression Atom %Undefined%)— the error-transparent twin oflet*. The form is shown but not run here: this engine leaves the call unreduced.unify%(-> Atom Atom Atom Atom %Undefined%)— the error-transparent twin ofunify. The form is shown but not run here: this engine leaves the call unreduced.=%(-> $t $t %Undefined%)— the error-transparent twin of=, the equation head itself. The form is shown but not run here: this engine does not declare the name.
Spaces
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(get-atoms &pb) | space += (S.f, 1) ⏎ space.atoms() | (f 1) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-atoms &pb ((f 1) (f 2))) ⏎ !(get-atoms &pb) | space += [(S.f, 1), (S.f, 2)] ⏎ space.atoms() | (f 1), (f 2) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-reduct &pb (total (+ 1 2))) ⏎ !(get-atoms &pb) | space += S.total(m.eval(S['+'](1, 2))[0]) ⏎ space.atoms() | (total 3) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-reducts &pb ((total (+ 1 2)) (total (+ 2 2)))) ⏎ !(get-atoms &pb) | for term in [S['+'](1, 2), S['+'](2, 2)]: ⏎ space += S.total(m.eval(term)[0]) ⏎ space.atoms() | (total 3), (total 4) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(remove-atom &pb (f 1)) ⏎ !(get-atoms &pb) | space += S.f(1) ⏎ del space[S.f(1)] ⏎ space.atoms() | (no answer) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(add-atom &pb (f 1)) ⏎ !(subtract-atom &pb (f 1)) ⏎ !(get-atoms &pb) | space += S.f(1) ⏎ space += S.f(1) ⏎ space -= S.f(1) ⏎ space.atoms() | (f 1) | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(get-atoms &pb) | space += S.f(1) ⏎ list(space) | (f 1) | method |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(match &pb (f $x) $x) | space += S.f(1) ⏎ assert space.match(S.f(V.x), under=metta.counting).one() == 1 ⏎ space.run('(= (phrasebook-call) yes)') ⏎ assert space.answers(S.phrasebook_call(), under=metta.counting).one() == 1 ⏎ with metta.under(metta.prov): ⏎ annotated = space.match(S.f(V.x)).one() ⏎ assert annotated.annotation == S.one ⏎ assert annotated.under(metta.counting).annotation == 1 ⏎ assert annotated.why() ⏎ declared = metta.algebra(S.phrasebook_max_plus, plus=max, times=lambda a, b: a + b, zero=-100, one=0, order='descending') ⏎ assert declared.name == 'phrasebook-max-plus' ⏎ [row['x'] for row in space[S.f(V.x)]] | 1 | method |
!(bind! &pb (new-space)) ⏎ !(match% &pb (f $x) $x) | — | — | absent |
!(get-atoms (new-space)) | list(metta.space()) | (no answer) | method |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(get-atoms (fork-space &pb)) | space += S.f(1) ⏎ space.copy().atoms() | (f 1) | method |
!(add-atom &self (f 1)) ⏎ !(get-atoms &self) | space += S.f(1) ⏎ space.atoms() | (f 1) | dissolves |
!(add-atom (context-space) (f 1)) ⏎ !(get-atoms (context-space)) | space += S.f(1) ⏎ space.atoms() | (f 1) | method |
!(mod-space! stdlib) | — | — | absent |
!(module-space-no-deps (new-space)) | — | — | absent |
!(get-deps stdlib) | — | — | absent |
add-atom(-> SpaceType Atom (->))—space += atom, the container protocol. A plain Python tuple encodes to an expression on the way in, so a fact needs no builder ceremony. Bare symbols, grounded values, and empty expressions cross when engineadd-atomaccepts them too.add-atoms(-> SpaceType Expression (->))— The same+=form, once per fact: anything that yields tuples is a fact stream. Lists, outer tuples of rows, generators, SQL cursors, and dataframe row iterators each write one atom per yielded item; a built Expression is always one atom.add-reduct(-> SpaceType %Undefined% (->))— There is no second form:+=adds what you give it, so adding a REDUCT is explicit composition,space += m.eval(term)[0]. Bare grounded answers use+=directly; this row wraps the evaluated sum only to retain itstotalrelation head. Where they differ: This engine stores(total (+ 1 2))UNREDUCED where the Python composition both store(total 3): this engine's add-reduct does not reduce inside an expression whose head has no equations.add-reducts(-> SpaceType %Undefined% (->))— The plural of the same composition: evaluate, then write the answers. Where they differ: This engine stores both forms UNREDUCED where the Python composition store(total 3)and(total 4), the same non-reduction asadd-reduct.remove-atom(-> SpaceType Atom Bool)— Drains every atom that unifies and answers True either way.del space[pattern]is this operation, and raises when the pattern matches nothing as Python'sdeldoes;subtract-atomis the one-occurrence grain beside it, whichspace -= atomandspace.remove(atom)both spell.subtract-atom(-> SpaceType Atom Bool)— Takes ONE unifying occurrence and answers whether one was there, the multiset subtractionremove-atomgave up when it took upstream's draining law.space -= atomis this operation, because Python's in-place difference over a multiset iscollections.Counter's, which subtracts the multiplicity given rather than clearing the key, andspace.remove(atom)is the same grain reporting what it found. An unbound atom is refused by name rather than read as every atom at once.get-atoms(-> SpaceType Atom)—space.atoms(), orfor atom in spacewhen you want to walk them.match(-> SpaceType Atom Atom %Undefined%)—space[pattern]is the subscript form andspace.match(pattern)the named method; the TEMPLATE is built in Python from the answer's bindings.under=counting|tropical|prov|rankedchanges the annotation algebra;answers(call, under=...)is its call twin,with metta.under(...)scopes the default, and an annotated answer exposes.annotation,.why()and.under(other)without a re-query.metta.algebra(...)constructs arbitrary carriers while remaining their namespace.match%(-> SpaceType Atom Atom %Undefined%)— the error-transparent twin ofmatch. The form is shown but not run here: this engine leaves the call unreduced.new-space(-> SpaceType)—metta.space(). A constructor call is Python's own spelling formake me a fresh one, and the row asks the fresh space for its atoms because the NAME a space gets differs per engine.fork-space(-> SpaceType SpaceType)—space.copy(), which answers an independent space: writing to the copy leaves the original alone [measured 2026-08-22]. The form is shown but not run here: this engine leaves the MeTTa call unreduced.&selfSpaceType— The space you are in, which in Python is the handle you already hold:mfor the engine's own space,spacefor a named one. A name spelt as a symbol is what a Python binding is for.context-space(-> SpaceType)— The space a program is currently in, which in Python is the handle it holds;metta.current_space()is the function for code that did not receive one, and it follows Python's owncurrent_threadandcurrent_taskconvention, so the Python word wins over the instruction's name. The row asks both sides for the current space's atoms.mod-space!(-> Atom SpaceType)— The space of a loaded module. MeTTa's module story is Python packaging, so the name has no image here. The form is shown but not run here: this engine leaves the call unreduced.module-space-no-deps(-> SpaceType SpaceType)— A module's own space without its dependencies. Same module story. The form is shown but not run here: this engine leaves the call unreduced.get-deps(-> Atom Atom)— A loaded module's direct dependency names. Same module story: MeTTa's module story is Python packaging, so the name has no image here. The form is shown but not run here: this engine leaves the call unreduced.
Types
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(get-type 1) | m.type(1) | Number | method |
!(get-type-space &self 1) | space.type(1) | Number | method |
!(get-metatype (a b)) | e = metta.Expression(S.a, S.b) ⏎ S[e.metatype] | Expression | dissolves |
!(is-function (-> Number Number)) | t = S['->'](S.Number, S.Number) ⏎ t[0] == S['->'] | True | dissolves |
(: pbf (-> Number Number)) ⏎ (= (pbf $x) $x) ⏎ !(get-type pbf) | t = S['->'](S.Number, S.Number) ⏎ t | (-> Number Number) | dissolves |
(= (pbf $x) (+ $x 1)) ⏎ !(pbf 1) | space += metta.equation(S.pbf(V.x)).to(V.x + 1) ⏎ space.eval(S.pbf(1))[0] | 2 | method |
!(get-type &self) | — | — | absent |
!(get-type TP) | space += metta.lib.strategy ⏎ space.eval(S['get-type'](metta.strategies.TP)) | Type | method |
!(get-type TU) | space += metta.lib.strategy ⏎ space.eval(S['get-type'](metta.strategies.TU)) | (-> Type Type) | method |
!(get-type (Pair 1 2)) | — | — | absent |
!(get-type PairType) | — | — | absent |
!(skel-swap-pair (Pair 1 2)) | — | — | absent |
!(skel-swap-pair-native (Pair 1 2)) | — | — | absent |
!(get-type ◁) | space += metta.lib.strategy ⏎ space.eval(S['get-type'](S['◁'])) | (-> Atom Type Atom Atom) | method |
get-type(-> Atom %Undefined%)— Declared types are space-relative, sospace.type(atom)asks the space. Class declarations use the consolidated@space.definedecorator.get-type-space(-> SpaceType Atom Atom)— The same question asked of a named space through that handle'sspace.type(atom)method.get-metatype(-> Atom Atom)— Python's own builtintype: the four atom classes ARE the four metatypes, sotype(a).__name__is the metatype by construction.is-function(-> Type Bool)— Asking whether a type is an arrow. In Python the same question is asked of the annotation, andm.is_function(name)asks it of a defined name.->(-> (%Rest% Type) Type)— Annotations. A parameter and return annotation on a decorated function emits the arrow, andCallable[[int], int]maps through the same one table;S['->']stays for a hand-built arrow.=(-> $t $t %Undefined%)— The definitional decorator.@m.definecompiles a function into equations,metta.equation(lhs).to(rhs)builds one by hand, and both land as ordinary(= ...)atoms a program can match.SpaceTypeType— The type of a space. This engine does not declare the name, so there is nothing for a Python type table to map to yet. The form is shown but not run here: this engine answers SpaceType for a space but does not declare the symbol itself.TPType— Lämmel's type-preserving strategy scheme, exported as the reifiedmetta.strategies.TPsymbol.TU(-> Type Type)— Lämmel's type-unifying scheme constructor, exported as the reifiedmetta.strategies.TUsymbol.Pair(-> $ta $tb (PairType $ta $tb))— A constructor from theskeldemonstration module. This engine has no such module; a class decorated with@space.definedeclares its constructor in that space. The form is shown but not run here: this engine does not declare the name.PairType(-> $ta $tb Type)— The parameterised type ofPair, from the same module. The form is shown but not run here: this engine does not declare the name.skel-swap-pair(-> (PairType $ta $tb) (PairType $tb $ta))— Theskelmodule's worked equation, the demonstration that a built-in module can ship both a MeTTa and a native implementation. The form is shown but not run here: this engine does not declare the name.skel-swap-pair-native(-> (PairType $ta $tb) (PairType $tb $ta))— The native half of the same demonstration. The form is shown but not run here: this engine does not declare the name.◁(-> Atom Type Atom Atom)— The typed strategy-application atom selects the TP or TU scheme before running the named strategy.
The state cell
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(get-state (new-state 1)) | state = metta.State[int](1, space=m) ⏎ def retain(cell, event): ⏎ cell.value += int(event.n) ⏎ folded = m.events().fold(retain, space=space.name, pattern=S.delta(V.n), into=state) ⏎ space += S.delta(0) ⏎ folded.cancel() ⏎ state.value | 1 | method |
!(let $c (new-state 5) (get-state $c)) | state = metta.State[int](5, space=m) ⏎ state.value | 5 | method |
!(let $c (new-state 1) (let $_ (change-state! $c 2) (get-state $c))) | state = metta.State[int](1, space=m) ⏎ state.value = 2 ⏎ state.value | 2 | method |
new-state(-> $t (StateMonad $t))—metta.State[T](value, space=space)creates the typed Python handle. The row reads.valuebecause the engine cell itself is deliberately hidden behind that handle. An eventfold(..., into=state)passes this same process-shared cell to its step; individual reads and writes are thread-safe, but a compound read-modify-write needs coordination.get-state(-> (StateMonad $tgso) $tgso)— Reading the cell is the typed handle'sstate.valueproperty.change-state!(-> (StateMonad $tcso) $tcso Bool)— Assigningstate.valuewrites the same typed engine cell and reading it back returns the replacement. The WRITE answers True rather than the cell, which is upstream's own answer --'change-state!'(Var, Value, true)[source: PeTTa@ae66fa8 src/metta.pl:265] -- so the read is a separate step here as it is in Python.
Printing and text
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(println! hello) | print('hello') ⏎ metta.TRUE | True | dissolves |
!(trace! hello (+ 1 2)) | print('hello') ⏎ 1 + 2 | 3 | dissolves |
!(format-args "{} and {}" (a b)) | a, b = S.a, S.b ⏎ f'{a} and {b}' | "a and b" | dissolves |
!(print-alternatives! subject (a b)) | print(S.subject, [S.a, S.b]) ⏎ metta.Expression() | () | dissolves |
println!(-> %Undefined% Bool)— Python'sprint. It answers True rather than unit, which is upstream's own answer:'println!'(Arg, true)[source: PeTTa@ae66fa8 src/metta.pl:212]. Where they differ: this engine and Python answer True, which is upstream's own'println!'(Arg, true)[source: PeTTa@ae66fa8 src/metta.pl:212].trace!(-> %Undefined% Atom %Undefined%)—printorloggingbeside the value;m.trace()is the engine's own reduction trace, a different and deeper thing.format-args(-> String Expression String)— An f-string. MeTTa's{}holes are Python's own interpolation.print-alternatives!(-> Atom Expression (->))— Python'sprintover the answers, which is what the assert family uses it for: showing what a form actually answered. The form is shown but not run here: this engine leaves the MeTTa call unreduced.
Testing
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(assert (== 1 1)) | assert 1 == 1 ⏎ True | True | dissolves |
!(assertEqual (+ 1 1) 2) | assert m.eval(S['+'](1, 1))[0] == 2 ⏎ True | True | dissolves |
!(assertEqualMsg (+ 1 1) 2 "sums") | assert m.eval(S['+'](1, 1))[0] == 2, 'sums' ⏎ True | True | dissolves |
!(assertAlphaEqual (f $x) (f $y)) | assert S.f(V.x).alpha_eq(S.f(V.y)) ⏎ True | True | dissolves |
!(assertAlphaEqualMsg (f $x) (f $y) "renaming") | assert S.f(V.x).alpha_eq(S.f(V.y)), 'renaming' ⏎ True | True | dissolves |
!(assertEqualToResult (superpose (1 2)) (1 2)) | assert m.eval(S.superpose(metta.Expression(1, 2))) == [1, 2] ⏎ True | True | dissolves |
!(assertEqualToResultMsg (superpose (1 2)) (1 2) "both") | assert m.eval(S.superpose(metta.Expression(1, 2))) == [1, 2], 'both' ⏎ True | True | dissolves |
!(assertAlphaEqualToResult (f $x) ((f $y))) | assert m.eval(S.f(V.x))[0].alpha_eq(S.f(V.y)) ⏎ True | True | dissolves |
!(assertAlphaEqualToResultMsg (f $x) ((f $y)) "renaming") | assert m.eval(S.f(V.x))[0].alpha_eq(S.f(V.y)), 'renaming' ⏎ True | True | dissolves |
!(assertIncludes (superpose (a b)) (a)) | assert S.a in m.eval(S.superpose(metta.Expression(S.a, S.b))) ⏎ True | True | dissolves |
assert(-> Atom (->))— Python's ownassert. A twin or a test states its claims this way and the run proves them, because a false assertion raises.assertEqual(-> Atom Atom (->))—assert a == b, and pytest's own assertion rewriting prints the halves.assertEqualMsg(-> Atom Atom Atom (->))—assert a == b, message, which is Python's own second argument.assertAlphaEqual(-> Atom Atom (->))—assert a.alpha_eq(b): the assertion is Python's, the relation is MeTTa's.assertAlphaEqualMsg(-> Atom Atom Atom (->))— The same with Python's assertion message.assertEqualToResult(-> Atom Atom (->))— The right-hand side is a LIST of expected answers rather than one, which isassert list(answers) == [...].assertEqualToResultMsg(-> Atom Atom Atom (->))— The same with Python's assertion message.assertAlphaEqualToResult(-> Atom Atom (->))— The answer-list form compared modulo renaming.assertAlphaEqualToResultMsg(-> Atom Atom Atom (->))— The same with Python's assertion message.assertIncludes(-> Atom Expression (->))— Python's ownin.
Documentation
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(get-doc &self +) | def slug(title): ⏎ 'Make a title into a slug.' ⏎ slug.__doc__ | (no answer) on metta; "Make a title into a slug." on python | dissolves |
!(@doc pbf (@desc "adds one")) | def pbf(x): ⏎ 'adds one' ⏎ pbf.__doc__ | (@doc pbf (@desc "adds one")) on metta; "adds one" on python | dissolves |
!(@desc "adds one") | 'adds one' | (@desc "adds one") on metta; "adds one" on python | dissolves |
!(@param "the addend") | 'the addend' | (@param "the addend") on metta; "the addend" on python | dissolves |
!(@params ((@param "the addend"))) | ['the addend'] | (@params ((@param "the addend"))) on metta; "the addend" on python | dissolves |
!(@return "the sum") | 'the sum' | (@return "the sum") on metta; "the sum" on python | dissolves |
!(@type Number) | def pbf(x: int) -> int: ⏎ 'adds one' ⏎ pbf.__annotations__['return'] | (@type Number) on metta; "int" on python | dissolves |
!(@item pbf) | S.pbf | (@item pbf) on metta; pbf on python | dissolves |
!(@doc-formal (@item pbf) (@kind function) (@type (-> Number Number)) (@desc "adds one")) | def pbf(x: int) -> int: ⏎ 'adds one' ⏎ (pbf.__annotations__['return'], pbf.__doc__) | (@doc-formal (@item pbf) (@kind function) (@type (-> Number Number)) (@desc "adds one")) on metta; ("int" "adds one") on python | dissolves |
!(help! +) | def pbf(x): ⏎ 'adds one' ⏎ pbf.__doc__ | "adds one" | dissolves |
get-doc(-> SpaceType Atom %Undefined%)— Python's builtinhelp, over the docstring a decorated function already carries. This engine answers nothing here because no documentation atoms are written yet, which is the doc-vocabulary gap. Where they differ: the mechanised interpreter answers the full@doc-formalstructure for+; this engine answers nothing, because nothing emits documentation atoms.@doc(-> Atom DocDescription DocInformal) | (-> Atom DocDescription DocParameters DocReturnInformal DocInformal)— A docstring. One docstring is meant to feed both worlds: Python'shelpand the engine'sget-doc, once the emission lands. Where they differ: the MeTTa side is a CONSTRUCTOR and stays unreduced on both engines, which is correct; the Python side shows the same text.@desc(-> String DocDescription)— The description line of a docstring. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@param(-> String DocParameterInformal) | (-> DocType DocDescription DocParameter)— One parameter's line in a docstring, which the docstring convention already carries. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@params(-> Expression DocParameters)— The parameter block of a docstring. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@return(-> String DocReturnInformal) | (-> DocType DocDescription DocReturn)— The return line of a docstring. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@type(-> Type DocType)— The type shown in documentation, which annotations already supply. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@item(-> Atom DocItem)— The subject a documentation record is about, which in Python is the object the docstring hangs on. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.@doc-formal(-> DocItem DocKindFunction DocType DocDescription DocParameters DocReturn DocFormal) | (-> DocItem DocKindAtom DocType DocDescription DocFormal) | (-> DocItem DocKindFunction DocType DocDescription DocFormal)— The whole documentation record, which a typed and docstringed Python function already is: signature plus prose in one place. Where they differ: the MeTTa side is a constructor and stays unreduced, correctly.help!(-> Atom (->)) | (-> (->))— Python's builtinhelp, which is the same act on the same docstring. Where they differ: the mechanised interpreter prints the documentation and answers the unit; this engine leaves the call unreduced because it declares no documentation. The form is shown but not run here: this engine leaves the call unreduced.
Modules and imports
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(import! &self (library lib_he)) ⏎ !(unify (f a) (f $x) $x nope) | import math ⏎ S[math.__name__] | a on metta; math on python | dissolves |
!(import-into! (new-space) (library lib_he)) | — | — | absent |
!(import-item! &self (library lib_he) unify) | — | — | absent |
!(get-metatype include) | import pathlib, tempfile ⏎ path = pathlib.Path(tempfile.mkdtemp()) / 'inc.metta' ⏎ path.write_text('(= (pbi) 7)\n') ⏎ space.load(str(path)) ⏎ space.eval(S.pbi())[0] | Grounded on metta; 7 on python | method |
!(get-metatype git-import!) | import importlib ⏎ S[importlib.import_module('json').__name__] | Grounded on metta; json on python | method |
!(get-metatype git-module!) | — | — | absent |
!(get-metatype register-module!) | — | — | absent |
!(print-mods!) | import sys ⏎ print(len(sys.modules), 'modules') ⏎ metta.Expression() | () | dissolves |
!(loaded-mods!) | import sys ⏎ S['json'] if 'json' in sys.modules else S.absent | json | dissolves |
!(module-tree!) | import importlib.metadata ⏎ S[importlib.metadata.requires.__name__] | requires | dissolves |
!(bind! &pb (new-space)) ⏎ !(add-atom &pb (f 1)) ⏎ !(get-atoms &pb) | space += S.f(1) ⏎ space.atoms() | (f 1) | dissolves |
import!(-> Atom Atom (->))— Python's ownimport, and for a MeTTa library the boot manifest orm.load(path). The module catalog IS Python packaging. Where they differ: MeTTa imports a MeTTa library into a space where Python imports a Python module into a namespace.import-into!(-> SpaceType Atom (->))— Importing into a NAMED space rather than the current one. MeTTa's loader does not offer it. The form is shown but not run here: this engine leaves the call unreduced.import-item!(-> Atom Atom Atom (->))— Importing one named item, which is Python'sfrom x import y. Not implemented here. The form is shown but not run here: this engine leaves the call unreduced.include(-> Atom %Undefined%)—space.load(path)reads a file into that space, which is what include does; Python's ownimportis the spelling for a Python module. Where they differ: no file path is portable between the two engines, so the MeTTa column shows only that the name is a grounded operation while the Python column loads a real file and calls what it defined.git-import!(-> String String Atom)— pip andimportlib. Fetching a dependency is packaging's job, the module catalog IS Python packaging, and a boot manifest names the distribution. Where they differ: a row cannot fetch a repository, so the MeTTa column shows only that the name is a grounded operation while the Python column imports a distribution that is already installed.git-module!(-> Atom (->))— Upstream's bespoke package manager. The form is shown but not run here: this engine does not declare the name. Ruled rather than missing: decision 8: the module catalog IS Python packaging, and upstream's bespoke manager is the fork not taken, so the absence is a decision rather than a gap.register-module!(-> Atom (->))— Registering a module with the bespoke catalog. A Python distribution registers itself by declaring an entry point, which pip then installs. The form is shown but not run here: this engine does not declare the name. Ruled rather than missing: decision 8: pip and entry-point discovery are the catalog, so the absence is a decision rather than a gap.print-mods!(-> (->))—print(sorted(sys.modules)). Under the ruling that the module catalog IS Python packaging, the loaded-module question is Python's own. Where they differ: MeTTa modules there, Python modules here, which is what the ruling makes them. The form is shown but not run here: this engine does not declare the name.loaded-mods!(-> Atom)—sys.modules, the same list as data rather than printed. Where they differ: MeTTa modules there, Python modules here. The form is shown but not run here: this engine does not declare the name.module-tree!(-> Atom)—importlib.metadata.requires(name), which answers the dependency tree a distribution declares. The row names the operation rather than a package, because no distribution is guaranteed installed wherever the lane runs. Where they differ: the trees are different: MeTTa modules there, installed distributions here. The form is shown but not run here: this engine does not declare the name.bind!(-> Symbol %Undefined% (->))— A Python name binding.space = metta.space(...)is exactly what a token binding was for, and Python's own scoping rules then apply.
Errors
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(Error a b) | S.Error(S.a, S.b) | (Error a b) | dissolves |
!(get-type (Error a b)) | S.ErrorType | ErrorType | dissolves |
!(get-type BadType) | — | — | absent |
!(get-type BadArgType) | — | — | absent |
!(get-type IncorrectNumberOfArguments) | — | — | absent |
!(if-error (Error a b) yes no) | e = S.Error(S.a, S.b) ⏎ S.yes if e[0] == S.Error else S.no | yes | dissolves |
!(return-on-error a b) | value = S.a ⏎ value if isinstance(value, metta.Expression) and value[0] == S.Error else S.b | b | dissolves |
!(_separate-errors ((Error a b) c) ()) | answers = [S.Error(S.a, S.b), S.c] ⏎ [a for a in answers if isinstance(a, metta.Expression) and a[0] == S.Error] | (Error a b) | dissolves |
Error(-> Atom Atom ErrorType)— An exception. A Python operation raises and the boundary maps the exception INTO this algebra rather than inventing a parallel one. The constructor itself never reduces, on either engine, which is correct.ErrorTypeType— The type an error atom carries, which on the Python side is the exception class.BadType(-> Type Type ErrorDescription)— The canonical wrong-type error description. MeTTa does not declare the name, which is the error-vocabulary gap ledger X names. The form is shown but not run here: this engine does not declare the name.BadArgType(-> Number Type Type ErrorDescription)— The positional form,(BadArgType <pos> <expected> <actual>). Same gap. The form is shown but not run here: this engine does not declare the name.IncorrectNumberOfArgumentsErrorDescription— The arity error description, which Python's ownTypeErroris the image of. Same gap. The form is shown but not run here: this engine does not declare the name.if-error(-> Atom Atom Atom %Undefined%)—try/except, or a conditional over the value. It is the railway combinator over Error atoms.return-on-error(-> Atom Atom %Undefined%)— Early return, which is Python's ownreturninside anif. Indexing needs the guard because a leaf atom is not indexable here._separate-errors(-> Expression Expression Expression)— Partitioning answers into errors and results, which is one comprehension per side. The form is shown but not run here: this engine leaves the call unreduced.
Rewriting strategies
| MeTTa | Python | answers | bucket |
|---|---|---|---|
(= (pb-try-step strategy-a) strategy-b) ⏎ (= (pb-try-step $x) Empty) ⏎ !(strategy-apply (try pb-try-step) strategy-a) | space += metta.lib.strategy ⏎ space.run('(= (pb-try-step strategy-a) strategy-b) (= (pb-try-step $x) Empty)') ⏎ space.eval(S['strategy-apply'](metta.strategies.try_(S['pb-try-step']), S['strategy-a'])) | strategy-b | method |
(= (pb-repeat-step strategy-a) strategy-b) ⏎ (= (pb-repeat-step strategy-b) strategy-c) ⏎ (= (pb-repeat-step $x) Empty) ⏎ !(strategy-apply (repeat pb-repeat-step) strategy-a) | space += metta.lib.strategy ⏎ space.run('(= (pb-repeat-step strategy-a) strategy-b) (= (pb-repeat-step strategy-b) strategy-c) (= (pb-repeat-step $x) Empty)') ⏎ space.eval(S['strategy-apply'](metta.strategies.repeat(S['pb-repeat-step']), S['strategy-a'])) | strategy-c | method |
(= (pb-topdown-step strategy-a) strategy-b) ⏎ (= (pb-topdown-step $x) Empty) ⏎ !(strategy-apply (topdown (try pb-topdown-step)) (strategy-node strategy-a)) | space += metta.lib.strategy ⏎ space.run('(= (pb-topdown-step strategy-a) strategy-b) (= (pb-topdown-step $x) Empty)') ⏎ plan = metta.strategies.topdown(metta.strategies.try_(S['pb-topdown-step'])) ⏎ space.eval(S['strategy-apply'](plan, S['strategy-node'](S['strategy-a']))) | (strategy-node strategy-b) | method |
(= (pb-bottomup-step strategy-a) strategy-b) ⏎ (= (pb-bottomup-step (strategy-node strategy-b)) strategy-bottomup-root) ⏎ (= (pb-bottomup-step $x) Empty) ⏎ !(strategy-apply (bottomup (try pb-bottomup-step)) (strategy-node strategy-a)) | space += metta.lib.strategy ⏎ space.run('(= (pb-bottomup-step strategy-a) strategy-b) (= (pb-bottomup-step (strategy-node strategy-b)) strategy-bottomup-root) (= (pb-bottomup-step $x) Empty)') ⏎ plan = metta.strategies.bottomup(metta.strategies.try_(S['pb-bottomup-step'])) ⏎ space.eval(S['strategy-apply'](plan, S['strategy-node'](S['strategy-a']))) | strategy-bottomup-root | method |
(= (pb-innermost-step strategy-a) strategy-b) ⏎ (= (pb-innermost-step strategy-b) strategy-c) ⏎ (= (pb-innermost-step (strategy-node strategy-c)) strategy-innermost-root) ⏎ (= (pb-innermost-step $x) Empty) ⏎ !(strategy-apply (innermost pb-innermost-step) (strategy-node strategy-a)) | space += metta.lib.strategy ⏎ space.run('(= (pb-innermost-step strategy-a) strategy-b) (= (pb-innermost-step strategy-b) strategy-c) (= (pb-innermost-step (strategy-node strategy-c)) strategy-innermost-root) (= (pb-innermost-step $x) Empty)') ⏎ plan = metta.strategies.innermost(S['pb-innermost-step']) ⏎ space.eval(S['strategy-apply'](plan, S['strategy-node'](S['strategy-a']))) | strategy-innermost-root | method |
!(stratego-all id (f a b)) | space += metta.lib.strategy ⏎ plan = metta.strategies.stratego_all(metta.strategies.id) ⏎ space.eval(S['strategy-apply'](plan, S.f(S.a, S.b))) | (f a b) | method |
!(stratego-one id (f a b)) | space += metta.lib.strategy ⏎ plan = metta.strategies.stratego_one(metta.strategies.id) ⏎ space.eval(S['strategy-apply'](plan, S.f(S.a, S.b))) | (f a b), (f a b), (f a b) | method |
!(stratego-some id (f a b)) | — | — | absent |
!(eval-via-match (+ 1 2)) | — | — | absent |
!(eval-via-unify (+ 1 2)) | — | — | absent |
!(reduce-via-match (+ 1 2) x) | — | — | absent |
MeTTa's complete shipped basis is reified below. Every plan cell is ordinary queryable atom data, and every row is exercised by examples/ch20-extending-the-engine/20-02-metta-written-in-metta/11-strategy.metta through the normal library runner.
| public name | reified plan or MeTTa form | Python atom | law |
|---|---|---|---|
id | id | strategies.id | id(t) = t |
fail | fail | strategies.fail | answers no result |
seq | (seq s1 s2) | strategies.seq(s1, s2) | s2(s1(t)) |
choice | (choice left right) | strategies.choice(left, right) | complete left result bag, or right(t) only when that bag is empty |
try | (try s) | strategies.try_(s) | choice(s, id) |
repeat | (repeat s) | strategies.repeat(s) | try(seq(s, repeat(s))) |
all | (all s) | strategies.all(s) | apply s to every immediate child |
one | (one s) | strategies.one(s) | enumerate each successful one-child rewrite |
topdown | (topdown s) | strategies.topdown(s) | seq(s, all(topdown(s))) |
bottomup | (bottomup s) | strategies.bottomup(s) | seq(all(bottomup(s)), s) |
innermost | (innermost s) | strategies.innermost(s) | bottomup(try(seq(s, innermost(s)))) |
stratego-all | (stratego-all s) | strategies.stratego_all(s) | public alias of all(s) |
stratego-one | (stratego-one s) | strategies.stratego_one(s) | public alias of one(s) |
gtry | direct call only | S.gtry | gtry(s, t) = try(s)(t) |
strategy-apply | (strategy-apply s t) | S['strategy-apply'](s, t) | translator-lowers to the atom (strategy-eval s t) |
TP | TP | strategies.TP | type-preserving strategy scheme |
TU | (TU result-type) | strategies.TU(result_type) | type-unifying strategy scheme |
◁ | (◁ s TP t) or (◁ s (TU r) t) | S['◁'](s, scheme, t) | apply only when the declared strategy arrow fits the scheme |
tryTP | (-> Atom Atom)— Stratego'stry(s) = s <+ id. This engine reifiessin the plan and the mechanised interpreter specialises the same law to one equality rewrite. Unary form:(= strategy-a strategy-b) ⏎ !(try strategy-a).repeatTP | (-> Atom Atom)— Stratego'srepeat(s) = try(s ; repeat(s)), root steps to a normal form. Unary form:(= strategy-a strategy-b) ⏎ (= strategy-b strategy-c) ⏎ !(repeat strategy-a).topdownTP | (-> Atom Atom)— Stratego'stopdown(s) = s ; all(topdown(s)), preorder traversal. Unary form:(= strategy-a strategy-b) ⏎ (= (strategy-node strategy-b) strategy-bottomup-root) ⏎ !(topdown (strategy-node strategy-a)).bottomupTP | (-> Atom Atom)— Stratego'sbottomup(s) = all(bottomup(s)) ; s, postorder traversal. Unary form:(= strategy-a strategy-b) ⏎ (= (strategy-node strategy-b) strategy-bottomup-root) ⏎ !(bottomup (strategy-node strategy-a)).innermostTP | (-> Atom Atom)— Stratego'sinnermost(s) = bottomup(try(s ; innermost(s))). Unary form:(= strategy-a strategy-b) ⏎ (= strategy-b strategy-c) ⏎ (= (strategy-node strategy-c) strategy-innermost-root) ⏎ !(innermost (strategy-node strategy-a)).stratego-all(-> Atom Atom Atom)— Stratego'sall(s), applying a strategy to every immediate child.stratego-one(-> Atom Atom Atom)— Stratego'sone(s), applying to one child. the mechanised interpreter deliberately diverges from Stratego's committed choice by answering EVERY successful position through MeTTa's own nondeterminism.stratego-some(-> Atom Atom Atom)— Stratego'ssome(s), the third traversal primitive besideallandone: apply the strategy to every immediate child it succeeds on, keep each declining child as written, and fail when no child succeeded. The non-emptiness guard is the whole content, sinceallcomposed withgtrycan never fail. A the mechanised interpreter extension beyond corelib. The form is shown but not run here: this engine leaves the call unreduced.eval-via-match(-> Atom %Undefined%)— The one-step rewriting strategy the whole basis is specialised to. The form is shown but not run here: this engine leaves the call unreduced.eval-via-unify(-> Atom %Undefined%)— The unification-directed sibling ofeval-via-match. The form is shown but not run here: this engine leaves the call unreduced.reduce-via-match(-> Atom Atom %Undefined%)— The reduction form of the same strategy. The form is shown but not run here: this engine leaves the call unreduced.
Matching extensions
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(fuzzy-match (f a) ((f a) (f b)) 1) | — | — | absent |
!(fuzzy-match-space (new-space) (f a) ((f a)) 1) | — | — | absent |
!(fuzzy-match-context (new-space) (new-space) (f a) ((f a)) 1) | — | — | absent |
!(near-match (f a) ((f a) (f b)) 1) | — | — | absent |
!(sealed ($x) ($x $y)) | m.eval(S.sealed(metta.Expression(V.x), metta.Expression(V.x, V.y)))[0] | ($x $y#0) on metta; ($_v0 $_v1) on python | method |
!(capture (+ 1 2)) | — | — | absent |
fuzzy-match(-> Atom Expression Number Atom)— the cost-bounded approximate matcher, answering each candidate with its cost. MeTTa hasmetta.structuresfor many-to-one matching and no approximate matcher. The form is shown but not run here: this engine leaves the call unreduced.fuzzy-match-space(-> SpaceType Atom Expression Number Atom)— The same over a space's atoms. The form is shown but not run here: this engine leaves the call unreduced.fuzzy-match-context(-> SpaceType SpaceType Atom Expression Number Atom)— The same with a separate cost-declaration space. The form is shown but not run here: this engine leaves the call unreduced.near-match(-> Atom Expression Atom Atom)— The nearest-candidate form of the same family. The form is shown but not run here: this engine leaves the call unreduced.sealed(-> Expression Atom Atom)— Freshening every variable except a named few, the hygiene primitive under rule emission. The Python surface makes most uses unnecessary by construction, because a parameter-scoped rule is fresh per rule, so the row shows the law spelling. Where they differ: both freshen the second variable and keep the first, but the names differ: a variable built in Python comes back from the engine as$_96674rather than$x, so the two sides are alpha-equal and not string-equal.capture(-> Atom Atom)— Closing an atom over the current space. A Python function object already binds its engine and space, so the uses vanish; MeTTa does not implement the name. The form is shown but not run here: this engine leaves the call unreduced.
The minimal core
| MeTTa | Python | answers | bucket |
|---|---|---|---|
!(eval (+ 1 2)) | m.eval(S['+'](1, 2)) | 3 | method |
!(evalc (+ 1 2) &self) | space.eval(S['+'](1, 2)) | 3 | method |
!(metta (+ 1 2) %Undefined% &self) | m.eval(S['+'](1, 2)) | 3 | method |
!(chain (+ 1 2) $x (foo $x)) | x = m.eval(S['+'](1, 2))[0] ⏎ S.foo(x) | (foo 3) | dissolves |
!(function (return 5)) | — | — | absent |
!(function (return (+ 2 3))) | — | — | absent |
!(collapse-bind (superpose (a b))) | — | — | absent |
!(superpose-bind ((a (bindings)))) | — | — | absent |
eval(-> Atom Atom)— ONE step.m.eval(term)is the same one step and answers every result, andspace.eval(term)isevalc, the same step in a named space.evalc(-> Atom SpaceType Atom)— One step WITH an explicit context space, which isspace.eval(term): the signature IS term plus space.metta(-> Atom Type SpaceType Atom)— The full interpreter, which is what CALLING does: a defined object called from Python evaluates, andm.evalon a built term is the same act.chain(-> Atom Variable Atom %Undefined%)— Python assignment. Chain executes one instruction, binds, substitutes and continues, which is exactlyx = m.eval(t)[0]followed by use ofx.function(-> Atom Atom)— The core's function frame, whichreturncloses. MeTTa's compiled definitions do not go through this instruction and it is not implemented. The form is shown but not run here: this engine leaves the call unreduced.return(-> $t $t)— The core's return, paired withfunction: it is what closes the frame, so it only ever appears inside one. The form is shown but not run here: this engine leaves the call unreduced.collapse-bind(-> Atom Expression) | (TU Expression)— The deep-tier collapse that keeps each alternative's BINDINGS,((a (bindings ...)) ...). It belongs to the bindings-carrying tier, never to the surface; MeTTa's engine has the bindings carrier (answer_bindings) but not this instruction. The form is shown but not run here: this engine leaves the call unreduced.superpose-bind(-> Expression Atom)— The inverse ofcollapse-bind: it restores each alternative WITH its recorded bindings, which is a different operation fromsuperpose. The form is shown but not run here: this engine leaves the call unreduced.