Skip to content

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 spellingmeaningexample
@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 explainmetta.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 askanswers = 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 signatureanswer = relation(destination=S.Bob, source=S.Ada)
assert condition, reasoncontinue 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 -= atomremove every pattern match or one exact occurrence inside a compiled bodydel 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) imageterm = S.neg(V.x) ⏎ assert term == S['-'](0, V.x)
atom.cast(type_)admit an atom through the ambient space's type disciplinewith space: ⏎ person = S.Ann.cast('Person')
fresh()mint a helper-local variable that cannot capture a caller's namesprivate = fresh() ⏎ pattern = S.edge(private, V.value)
metta.catalogquery the runtime catalog as the ordinary &metta spacerows = metta.catalog.match(S.op(V.name, V.arity, V.kind))
repr(space())show the file and line that minted an anonymous spaceorigin = repr(metta.space())
space.register_token(re.compile(pattern), constructor)register a compiled text regex while preserving supported flagsspace.register_token(re.compile(r'kg', re.I), parse_mass)
testing.from_pattern(pattern)generate ground instances with repeated variable identity preservedinstances = testing.from_pattern(S.edge(V.x, V.x))
space.reacts(pattern, operation)declare the space's (on ...) reaction using the settled wordspace.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 FutureSpacefuture = 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 &mettaevents = metta._at("&metta").subscribe( ⏎ S["async-op"](S.fetch, V.space, V.phase), callback ⏎ )
metta.speculate()scope each default-context execution as a discarded segmentwith metta.speculate(): ⏎ metta.run(source)
space.covers(effect)declare the strongest effect rank a reified world of this space admitsspace.covers(EffectClass.writesState)
space.reify()capture an immutable evaluable world value, distinct from listing atomsworld = space.reify()
world.eval(target)evaluate without touching the parent and return answers plus a successor worldanswers, successor = world.eval(target)
space.commit(world)land the world's base-relative diff as ordinary post-commit writes and eventsspace.commit(successor)
space.compensates(operation, recovery)declare the operation that semantically reverses an effectful onespace.compensates(charge, refund)
space.saga(receipts)run forward steps whose committed (did ...) receipts drive reverse recoverywith 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

MeTTaPythonanswersbucket
!(+ 1 2)1 + 23dissolves
!(- 5 2)5 - 23dissolves
!(* 3 4)3 * 412dissolves
!(/ 7 2)7 / 23.5dissolves
!(% -7 3)-7 % 32dissolves
!(div-floor -7 2)-7 // 2-4dissolves
!(mod-floor -7 2)-7 % 21dissolves
!(div-trunc -7 2)import math ⏎ math.trunc(-7 / 2)-3dissolves
!(rem-trunc -7 2)import math ⏎ int(math.fmod(-7, 2))-1dissolves
!(div-euclid -7 2)a, b = -7, 2 ⏎ a // b if b > 0 else -(a // -b)-4dissolves
!(mod-euclid -7 2)a, b = -7, 2 ⏎ a % abs(b)1dissolves
  • + (-> 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 and mod-floor is 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 is math.trunc over 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.fmod is the truncating remainder, sign of the dividend; it answers a float, so an integer row wraps it in int. 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 is a % abs(b). The form is shown but not run here: this engine implements neither the floored nor the truncating division family.

Comparison and equality

MeTTaPythonanswersbucket
!(< 1 2)1 < 2Truedissolves
!(<= 2 2)2 <= 2Truedissolves
!(> 2 1)2 > 1Truedissolves
!(>= 1 2)1 >= 2Falsedissolves
!(== (f 1) (f 1))S.f(1) == S.f(1)Truedissolves
!(=alpha (f $x) (f $y))S.f(V.x).alpha_eq(S.f(V.y))Truemethod
!(noreduce-eq (+ 1 2) (+ 1 2))S['+'](1, 2) == S['+'](1, 2)Truedissolves
  • < (-> 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

MeTTaPythonanswersbucket
!(abs-math -3)abs(-3)3dissolves
!(sqrt-math 4)import math ⏎ math.sqrt(4)2.0dissolves
!(pow-math 2.0 3)2.0 ** 38.0dissolves
!(log-math 2 8)import math ⏎ math.log(8, 2)3.0dissolves
!(sin-math 0)import math ⏎ math.sin(0)0.0dissolves
!(cos-math 0)import math ⏎ math.cos(0)1.0dissolves
!(tan-math 0)import math ⏎ math.tan(0)0.0dissolves
!(asin-math 0)import math ⏎ math.asin(0)0.0dissolves
!(acos-math 1)import math ⏎ math.acos(1)0.0dissolves
!(atan-math 0)import math ⏎ math.atan(0)0.0dissolves
!(ceil-math 2.1)import math ⏎ math.ceil(2.1)3dissolves
!(floor-math 2.9)import math ⏎ math.floor(2.9)2dissolves
!(round-math 2.5)import math ⏎ math.floor(2.5 + 0.5)3dissolves
!(trunc-math 2.9)import math ⏎ math.trunc(2.9)2dissolves
!(isnan-math 1)import math ⏎ math.isnan(1)Falsedissolves
!(isinf-math 1)import math ⏎ math.isinf(1)Falsedissolves
  • abs-math (-> Number Number) — Python's builtin abs.
  • 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 as ceil-math.
  • round-math (-> Number Number) — NOT Python's round: round breaks a tie to the EVEN neighbour, so round(2.5) is 2 where MeTTa answers 3. Half away from zero is math.floor(x + 0.5) for a positive number.
  • trunc-math (-> Number Number)math.trunc, or int on a float.
  • isnan-math (-> Number Bool)math.isnan.
  • isinf-math (-> Number Bool)math.isinf.

Booleans

MeTTaPythonanswersbucket
!(and True False)True and FalseFalsedissolves
!(or True False)True or FalseTruedissolves
!(not True)not TrueFalsedissolves
!(xor True False)True ^ FalseTruedissolves
  • and (-> Bool Bool Bool) — Python's own keyword. On atoms & builds the MeTTa and instead, 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

MeTTaPythonanswersbucket
!(car-atom (a b c))e = metta.Expression(S.a, S.b, S.c) ⏎ e[0]adissolves
!(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)3dissolves
!(index-atom (a b c) 1)e = metta.Expression(S.a, S.b, S.c) ⏎ e[1]bdissolves
!(max-atom (1 2 3))max([1, 2, 3])3dissolves
!(min-atom (1 2 3))min([1, 2, 3])1dissolves
!(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() ⏎ result6dissolves
!(for-each-in-atom (1 2) println!)for value in [1, 2]: ⏎ print(value) ⏎ metta.Expression()(True True) on metta; () on pythondissolves
!(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 is e[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 builtin max over the children.
  • min-atom (-> %Undefined% Number) — Python's builtin min over the children.
  • sort-strings (-> Expression Expression) — Python's builtin sorted. A tuple goes back in as one expression.
  • map-atom (-> Expression Variable Atom Expression) | (-> Expression Expression Expression) — A comprehension, or map. 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 an if, or filter.
  • foldl-atom (-> Expression Atom Variable Variable Atom %Undefined%) | (-> Expression Atom Expression %Undefined%)functools.reduce with 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 (->)) — A for statement. It is called for its effect, so the row prints and answers the unit. Python's for has no value at all, and the concept map says None IS the unit, but metta.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, which Atom.map does over the whole term. Section 9e wants the bindings object to carry it, b.apply(template); metta.Bindings has 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 an if: the empty case is the else branch. The form is shown but not run here: this engine leaves the call unreduced.

Set operations

MeTTaPythonanswersbucket
!(union (superpose (a b b)) (superpose (b c)))[S.a, S.b, S.b] + [S.b, S.c]a, b, b, b, cdissolves
!(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, cdissolves
!(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, bdissolves
!(unique (superpose (a b b c)))list(dict.fromkeys([S.a, S.b, S.b, S.c]))a, b, cdissolves
!(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.Counter IS the multiset algebra, and & is its intersection.
  • subtraction (-> Atom Atom %Undefined%)Counter again, with -.
  • unique (-> Atom %Undefined%)dict.fromkeys is 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)Counter over children, answering an expression.
  • subtraction-atom (-> Expression Expression Atom)Counter over children, answering an expression.
  • unique-atom (-> Expression Atom)dict.fromkeys over children.

Control flow and nondeterminism

MeTTaPythonanswersbucket
!(if True a b)S.a if True else S.badissolves
!(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 ⏎ answertwodissolves
!(switch (+ 1 1) ((1 one) (2 two)))match 1 + 1: ⏎ case 1: ⏎ answer = S.one ⏎ case 2: ⏎ answer = S.two ⏎ answertwodissolves
!(let $x 1 (+ $x 1))x = 1 ⏎ x + 12dissolves
!(let* (($x 1) ($y 2)) (+ $x $y))x = 1 ⏎ y = 2 ⏎ x + y3dissolves
!(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()amethod
!(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, bdissolves
!(collapse (superpose (a b)))answers = [S.a, S.b] ⏎ tuple(answers)(a b)dissolves
!(id 5)55dissolves
!(nop 1 2)metta.Expression()()dissolves
!(if-equal a a yes no)S.yes if S.a == S.a else S.noyesdissolves
!(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))3method
!(gtry id a)space += metta.lib.strategy ⏎ space.eval(S.gtry(metta.strategies.id, S.a))amethod
!(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 own if, 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's match statement. A bare variable arm is case _.
  • switch (-> %Undefined% Expression %Undefined%) — Python's match statement again. switch differs from case only 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 into let*.
  • unify (-> Atom Atom Atom Atom %Undefined%) — Structural unification. metta.unify(a, b) symmetrically answers one substitution keyed by the VARIABLES, which is what atom.subs takes, or None; metta.unify(a, b, then, els) evaluates the engine conditional, running then once per binding set and els only 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, and yield is 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's pass, 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 with S[...] 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 as quote: a built term is already unevaluated.
  • unquote (-> %Undefined% %Undefined%) — Reducing a quoted term is m.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 of case. 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 of let. The form is shown but not run here: this engine leaves the call unreduced.
  • let*% (-> Expression Atom %Undefined%) — the error-transparent twin of let*. The form is shown but not run here: this engine leaves the call unreduced.
  • unify% (-> Atom Atom Atom Atom %Undefined%) — the error-transparent twin of unify. 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

MeTTaPythonanswersbucket
!(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)]]1method
!(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 engine add-atom accepts 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 its total relation 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 as add-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's del does; subtract-atom is the one-occurrence grain beside it, which space -= atom and space.remove(atom) both spell.
  • subtract-atom (-> SpaceType Atom Bool) — Takes ONE unifying occurrence and answers whether one was there, the multiset subtraction remove-atom gave up when it took upstream's draining law. space -= atom is this operation, because Python's in-place difference over a multiset is collections.Counter's, which subtracts the multiplicity given rather than clearing the key, and space.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(), or for atom in space when you want to walk them.
  • match (-> SpaceType Atom Atom %Undefined%)space[pattern] is the subscript form and space.match(pattern) the named method; the TEMPLATE is built in Python from the answer's bindings. under=counting|tropical|prov|ranked changes 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 of match. 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 for make 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.
  • &self SpaceType — The space you are in, which in Python is the handle you already hold: m for the engine's own space, space for 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 own current_thread and current_task convention, 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

MeTTaPythonanswersbucket
!(get-type 1)m.type(1)Numbermethod
!(get-type-space &self 1)space.type(1)Numbermethod
!(get-metatype (a b))e = metta.Expression(S.a, S.b) ⏎ S[e.metatype]Expressiondissolves
!(is-function (-> Number Number))t = S['->'](S.Number, S.Number) ⏎ t[0] == S['->']Truedissolves
(: 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]2method
!(get-type &self)absent
!(get-type TP)space += metta.lib.strategy ⏎ space.eval(S['get-type'](metta.strategies.TP))Typemethod
!(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, so space.type(atom) asks the space. Class declarations use the consolidated @space.define decorator.
  • get-type-space (-> SpaceType Atom Atom) — The same question asked of a named space through that handle's space.type(atom) method.
  • get-metatype (-> Atom Atom) — Python's own builtin type: the four atom classes ARE the four metatypes, so type(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, and m.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, and Callable[[int], int] maps through the same one table; S['->'] stays for a hand-built arrow.
  • = (-> $t $t %Undefined%) — The definitional decorator. @m.define compiles a function into equations, metta.equation(lhs).to(rhs) builds one by hand, and both land as ordinary (= ...) atoms a program can match.
  • SpaceType Type — 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.
  • TP Type — Lämmel's type-preserving strategy scheme, exported as the reified metta.strategies.TP symbol.
  • TU (-> Type Type) — Lämmel's type-unifying scheme constructor, exported as the reified metta.strategies.TU symbol.
  • Pair (-> $ta $tb (PairType $ta $tb)) — A constructor from the skel demonstration module. This engine has no such module; a class decorated with @space.define declares 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 of Pair, 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)) — The skel module'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

MeTTaPythonanswersbucket
!(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.value1method
!(let $c (new-state 5) (get-state $c))state = metta.State[int](5, space=m) ⏎ state.value5method
!(let $c (new-state 1) (let $_ (change-state! $c 2) (get-state $c)))state = metta.State[int](1, space=m) ⏎ state.value = 2 ⏎ state.value2method
  • new-state (-> $t (StateMonad $t))metta.State[T](value, space=space) creates the typed Python handle. The row reads .value because the engine cell itself is deliberately hidden behind that handle. An event fold(..., 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's state.value property.
  • change-state! (-> (StateMonad $tcso) $tcso Bool) — Assigning state.value writes 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

MeTTaPythonanswersbucket
!(println! hello)print('hello') ⏎ metta.TRUETruedissolves
!(trace! hello (+ 1 2))print('hello') ⏎ 1 + 23dissolves
!(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's print. 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%)print or logging beside 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's print over 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

MeTTaPythonanswersbucket
!(assert (== 1 1))assert 1 == 1 ⏎ TrueTruedissolves
!(assertEqual (+ 1 1) 2)assert m.eval(S['+'](1, 1))[0] == 2 ⏎ TrueTruedissolves
!(assertEqualMsg (+ 1 1) 2 "sums")assert m.eval(S['+'](1, 1))[0] == 2, 'sums' ⏎ TrueTruedissolves
!(assertAlphaEqual (f $x) (f $y))assert S.f(V.x).alpha_eq(S.f(V.y)) ⏎ TrueTruedissolves
!(assertAlphaEqualMsg (f $x) (f $y) "renaming")assert S.f(V.x).alpha_eq(S.f(V.y)), 'renaming' ⏎ TrueTruedissolves
!(assertEqualToResult (superpose (1 2)) (1 2))assert m.eval(S.superpose(metta.Expression(1, 2))) == [1, 2] ⏎ TrueTruedissolves
!(assertEqualToResultMsg (superpose (1 2)) (1 2) "both")assert m.eval(S.superpose(metta.Expression(1, 2))) == [1, 2], 'both' ⏎ TrueTruedissolves
!(assertAlphaEqualToResult (f $x) ((f $y)))assert m.eval(S.f(V.x))[0].alpha_eq(S.f(V.y)) ⏎ TrueTruedissolves
!(assertAlphaEqualToResultMsg (f $x) ((f $y)) "renaming")assert m.eval(S.f(V.x))[0].alpha_eq(S.f(V.y)), 'renaming' ⏎ TrueTruedissolves
!(assertIncludes (superpose (a b)) (a))assert S.a in m.eval(S.superpose(metta.Expression(S.a, S.b))) ⏎ TrueTruedissolves
  • assert (-> Atom (->)) — Python's own assert. 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 is assert 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 own in.

Documentation

MeTTaPythonanswersbucket
!(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 pythondissolves
!(@doc pbf (@desc "adds one"))def pbf(x): ⏎ 'adds one' ⏎ pbf.__doc__(@doc pbf (@desc "adds one")) on metta; "adds one" on pythondissolves
!(@desc "adds one")'adds one'(@desc "adds one") on metta; "adds one" on pythondissolves
!(@param "the addend")'the addend'(@param "the addend") on metta; "the addend" on pythondissolves
!(@params ((@param "the addend")))['the addend'](@params ((@param "the addend"))) on metta; "the addend" on pythondissolves
!(@return "the sum")'the sum'(@return "the sum") on metta; "the sum" on pythondissolves
!(@type Number)def pbf(x: int) -> int: ⏎ 'adds one' ⏎ pbf.__annotations__['return'](@type Number) on metta; "int" on pythondissolves
!(@item pbf)S.pbf(@item pbf) on metta; pbf on pythondissolves
!(@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 pythondissolves
!(help! +)def pbf(x): ⏎ 'adds one' ⏎ pbf.__doc__"adds one"dissolves
  • get-doc (-> SpaceType Atom %Undefined%) — Python's builtin help, 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-formal structure 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's help and the engine's get-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 builtin help, 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

MeTTaPythonanswersbucket
!(import! &self (library lib_he)) ⏎ !(unify (f a) (f $x) $x nope)import math ⏎ S[math.__name__]a on metta; math on pythondissolves
!(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 pythonmethod
!(get-metatype git-import!)import importlib ⏎ S[importlib.import_module('json').__name__]Grounded on metta; json on pythonmethod
!(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.absentjsondissolves
!(module-tree!)import importlib.metadata ⏎ S[importlib.metadata.requires.__name__]requiresdissolves
!(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 own import, and for a MeTTa library the boot manifest or m.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's from 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 own import is 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 and importlib. 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

MeTTaPythonanswersbucket
!(Error a b)S.Error(S.a, S.b)(Error a b)dissolves
!(get-type (Error a b))S.ErrorTypeErrorTypedissolves
!(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.noyesdissolves
!(return-on-error a b)value = S.a ⏎ value if isinstance(value, metta.Expression) and value[0] == S.Error else S.bbdissolves
!(_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.
  • ErrorType Type — 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.
  • IncorrectNumberOfArguments ErrorDescription — The arity error description, which Python's own TypeError is 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 own return inside an if. 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

MeTTaPythonanswersbucket
(= (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-bmethod
(= (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-cmethod
(= (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-rootmethod
(= (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-rootmethod
!(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 namereified plan or MeTTa formPython atomlaw
ididstrategies.idid(t) = t
failfailstrategies.failanswers 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)
gtrydirect call onlyS.gtrygtry(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)
TPTPstrategies.TPtype-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
  • try TP | (-> Atom Atom) — Stratego's try(s) = s <+ id. This engine reifies s in the plan and the mechanised interpreter specialises the same law to one equality rewrite. Unary form: (= strategy-a strategy-b) ⏎ !(try strategy-a).
  • repeat TP | (-> Atom Atom) — Stratego's repeat(s) = try(s ; repeat(s)), root steps to a normal form. Unary form: (= strategy-a strategy-b) ⏎ (= strategy-b strategy-c) ⏎ !(repeat strategy-a).
  • topdown TP | (-> Atom Atom) — Stratego's topdown(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)).
  • bottomup TP | (-> Atom Atom) — Stratego's bottomup(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)).
  • innermost TP | (-> Atom Atom) — Stratego's innermost(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's all(s), applying a strategy to every immediate child.
  • stratego-one (-> Atom Atom Atom) — Stratego's one(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's some(s), the third traversal primitive beside all and one: 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, since all composed with gtry can 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 of eval-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

MeTTaPythonanswersbucket
!(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 pythonmethod
!(capture (+ 1 2))absent
  • fuzzy-match (-> Atom Expression Number Atom) — the cost-bounded approximate matcher, answering each candidate with its cost. MeTTa has metta.structures for 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 $_96674 rather 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

MeTTaPythonanswersbucket
!(eval (+ 1 2))m.eval(S['+'](1, 2))3method
!(evalc (+ 1 2) &self)space.eval(S['+'](1, 2))3method
!(metta (+ 1 2) %Undefined% &self)m.eval(S['+'](1, 2))3method
!(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, and space.eval(term) is evalc, the same step in a named space.
  • evalc (-> Atom SpaceType Atom) — One step WITH an explicit context space, which is space.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, and m.eval on a built term is the same act.
  • chain (-> Atom Variable Atom %Undefined%) — Python assignment. Chain executes one instruction, binds, substitutes and continues, which is exactly x = m.eval(t)[0] followed by use of x.
  • function (-> Atom Atom) — The core's function frame, which return closes. 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 with function: 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 of collapse-bind: it restores each alternative WITH its recorded bindings, which is a different operation from superpose. The form is shown but not run here: this engine leaves the call unreduced.

Released under the MIT License.