OpenCog Hyperon

repository·main·Indexed 42 days ago

https://github.com/trueagi-io/hyperon-experimental

A development of OpenCog featuring MeTTa (Meta Type Talk), a language with clear semantics for meta-language features and various inference types. Hyperon provides a Rust-based core with C and Python APIs, allowing developers to extend MeTTa scripts with Python operations via the py_ops library. It includes tools such as the metta-py Python interpreter and the metta-repl Rust REPL.

Tokens
55.6K
Snippets
201
Records
266
Agent score
54%

What's inside Hyperon

  1. What is a MeTTa Module?

    main

    In the Hyperon/MeTTa ecosystem, a module is a unit of functionality designed for modularity rather than just package management.

    Definition: A module is a unit of functionality that can be loaded into a MeTTa runner and used by other MeTTa code without requiring modification of the module, regardless of the loading location or format.

    Key Characteristics:

    • Independence: A module should be testable independently of the client code that loads it.
    • Self-Contained Dependencies: A module should express its own sub-module dependencies and should not rely on the client to manually import sub-dependencies or configure the environment.
    • Isolation: A module's implementation should not be affected by the client's definitions (e.g., atoms or tokens) except through the module's defined interface.

    By analogy, modules behave more like Rust mods or shared libraries than C header files.

  2. Understand MeTTa elementary types

    main

    MeTTa uses several basic types. Most of these (except Type) affect the order of expression evaluation.

    Meta-types

    These correspond to the atom types defined in the grammar and are used to control evaluation order:

    • Atom: A special meta-type that matches any atom; used for changing evaluation order.
    • Symbol: The type of a symbol atom.
    • Variable: The type of a variable atom.
    • Expression: The type of an expression atom.
    • Grounded: The type of a grounded atom.

    Other Basic Types

    • Type: The type of any type.
    • %Undefined%: Represents an unknown type.
  3. Understand the Hyperon codebase structure

    main

    The Hyperon project is organized into several layers to support multiple languages:

    • Rust Core (libhyperon.rlib): The main library containing the core API. It is composed of three crates:
      • hyperon-common: Utility structures (collections, reference implementations).
      • hyperon-atom: Core API for third-party built-in module providers.
      • lib: MeTTa atomspace and interpreter implementations.
    • C API (libhyperonc): An export library located in ./c that provides Rust C API bindings. It depends on libhyperon.rlib and is used by non-Rust platforms.
    • Python Integration: Located in ./python. It consists of:
      • libhyperonpy: A native library using pybind11 to convert Python API calls to C API calls.
      • hyperon: A Python library that acts as a proxy for libhyperonpy.

    Components depending on libhyperonc are managed via CMake.

  4. Importing modules in MeTTa

    main

    MeTTa supports three distinct import behaviors for linking modules. Note that while these are discussed as language constructs, their implementation details vary in the current experimental codebase.

    1. Import As (import module as name): Registers a new Tokenizer entry in the destination module's Tokenizer within the sub-module's space. This allows the sub-module's space to be accessed within the client module context.
    2. Import All (import * from module): Conceptually overlays the entire contents (space and tokens) of a sub-module onto the client module. In the current implementation, this involves deep-cloning the sub-module's space and merging tokenizer entries.
    3. Import Item (import item from module as name): Imports a specific item from a module.

    Note: As of the current version, import item is implemented via MettaMod::import_item_from_dependency_as but lacks a stdlib operation, making it currently inaccessible from standard MeTTa code.

  5. Understand the MeTTa Interpreter State and Evaluation

    main

    The MeTTa interpreter evaluates atoms step-by-step using a non-deterministic evaluation model.

    Core Components:

    • Atomspace: A knowledge base that drives evaluation.
    • Bindings: A set of variable assignments (empty at start).
    • Interpretation Plan: A list of pairs <atom, bindings> representing possible evaluation branches. The interpreter processes all branches in parallel.

    Evaluation Process:

    1. An instruction is executed on an atom from the plan.
    2. The result is a set of new <atom, bindings> pairs.
    3. New bindings are merged with existing ones.
    4. All resulting pairs are added back to the plan for the next step.

    Note on Instructions: Only instructions from the 'minimal set' are interpreted. Other atoms are treated as data and returned as-is.

  6. Understanding Implementation vs Interface sub-modules

    main

    When managing dependencies in MeTTa, it is critical to distinguish between how a module uses its sub-modules:

    • Implementation sub-modules: These are dependencies used internally by a module. They do not interfere with other modules. For example, ModA and ModB can both import different versions of ModC as implementation sub-modules without conflict.
    • Interface sub-modules: These are dependencies that are part of a module's public interface. If ModA uses ModC in its interface, any client importing ModA will transitively import ModC. In this case, all modules in the dependency graph must reconcile to use the same version of ModC to avoid conflicts.
  7. MeTTa evaluation algorithm and the `metta` symbol

    main

    The core of the MeTTa interpreter is the metta symbol. It is a recursive function that allows calling the interpreter from within a MeTTa program itself.

    When an atom is evaluated, the metta symbol is called with four arguments:

    1. Atom: The atom to be evaluated.
    2. Expected Type: The type expected from the result (defaults to %Undefined%).
    3. Context Space: The atomspace providing the context.
    4. Variable Bindings: The current set of variable bindings (empty at the start of evaluation).

    The evaluation process involves several internal steps including type_cast, interpret_expression, interpret_tuple, and interpret_function. If an expression is evaluated, the interpreter may mark it as evaluated to optimize subsequent calls.

  8. How MeTTa evaluation order works

    main

    By default, MeTTa uses normal evaluation order (arguments are passed to functions without being evaluated first).

    While standard MeTTa can use applicative evaluation via meta-types, the minimal instruction set relies on this fixed normal order. To force an argument to be evaluated before being passed to a function, use the chain instruction.

  9. MeTTa variable bindings and matching syntax

    main

    Variable bindings in MeTTa consist of two types of relations:

    1. Assignment: Assigning a value to a variable using the <- arrow.
      • Example: $x <- SomeValue (assigns SomeValue to $x).
    2. Equality: Declaring two variables are equal using the = sign.
      • Example: $x = $y (means $x and $y have equal or matchable values).

    A full set of bindings is represented using curly braces, e.g., { $x <- SomeValue, $x = $y }. The order of relations within the set does not matter.

  10. MeTTa S-expression grammar and syntax

    main

    MeTTa programs consist of atoms, which can be prefixed by the ! sign. The grammar is based on S-expressions.

    Atom Types

    • SYMBOL: A WORD.
    • VARIABLE: Prefixed with $, e.g., $var or "$var".
    • GROUNDED: A STRING or WORD (e.g., integers or identifiers).
    • EXPRESSION: An S-expression enclosed in parentheses ( ... ) containing atoms.

    Syntax Rules

    • Variables: Must start with $. The # character is reserved for internal use (automatically generated variables) and cannot be used in variable names.
    • Symbols: Can start with !. For example, !name is a valid symbol.
    • Strings: Enclosed in double quotes "...".
    • Comments: Start with ; and continue until the end of the line (EOL).
    • Whitespace: Any character for which char.is_whitespace() in Rust returns true.
    METTA ::= { [ '!', [ DELIM ] ], ATOM, [ DELIM ] } ;
    
    ATOM ::= SYMBOL | VARIABLE | GROUNDED | EXPRESSION ;
    SYMBOL ::= WORD;
    VARIABLE ::= '$', ( CHAR | '"' ), { CHAR | '"' }
    GROUNDED ::= STRING | WORD ;
    EXPRESSION ::= '(', { ATOM, [ DELIM ] }, ')' ;
  11. Use function and return for complex evaluations in chain

    main

    When using chain, you may need to perform complex evaluations before making a substitution. To achieve this, wrap the evaluation in a function operation. The function operation evaluates its argument in a loop until a (return <atom>) is encountered, then returns that <atom> as the result.

    To perform a substitution using the result of a function evaluation, use the following pattern:

    (chain (function (return <atom>)) <var> <templ>)
  12. Understand variable scope in MeTTa

    main

    In MeTTa, each separately evaluated expression constitutes its own variable scope. Variable names are treated as unique within that expression.

    Key Rules:

    • Independence: If two separate expressions in the atomspace use the same variable name, they are different variables if they reside in independent scopes.
    • Assignment via Matching: When a variable is passed as an argument and matched by a value, the value is assigned to that variable. If a variable passed as an actual argument is matched by a formal argument variable, the formal variable references the actual one, allowing the actual variable to receive a value outside its immediate scope.

    Example of Scope Isolation:

    (= (foo) (function (chain (unify A $a () ()) $_ (return ()))))
    !(chain (eval (foo)) $_ $a)

    In this case, the value is NOT assigned to the $a from the caller because the variables reside in different scopes.