Nested methods GitHub issue

vibecode
{"vibecode": {
    "doc": "nested_methods",
    "role": "design notes on nested methods — methods grouped under a namespace path on a class. Replaces the older helper-as-sub-object model with namespaces-as-pure-organization. The .object namespace is the canonical example: it holds the universal-introspection methods so user classes don't inherit them into their top-level namespace.",
    "status": "active_design",
    "audience": "Miko and Claude collaborating on the design",
    "related": ["requirements/caspian/lucy/index.md (helpers — the older model this replaces)",
        "requirements/ecoverse/objects/index.md (object structure)",
        "requirements/ecoverse/worldlets/worldlet.json (records.b shows a 'beverage.nested.tea_earl_grey_hot' shape)"]
}}

Core idea GitHub issue

A nested method is a method declared under a namespace path on its class. Calling $foo.bar.gup() dispatches gup from the bar namespace on $foo's class. The namespace is organizational only — no sub-object is created, self inside gup is $foo, and gup has the same access to $foo's bucket and other methods that a top-level method would.

This replaces the older helper-as-sub-object model documented in caspian/lucy/index.md § Helpers. Under that model, $foo.bar returned a separate object holding a backreference to $foo, and helper methods could only reach back into the parent through that backreference's public surface. The new model collapses that: $foo.bar is no longer an object, it's a name prefix.

Why namespaces exist GitHub issue

vibecode
{"vibecode": {
    "section": "why_namespaces_exist",
    "role": "preserves the original motivation for the mechanism: keep base-class universal methods out of the way so user classes get a clean top-level namespace for their own methods"
}}

The original purpose of helpers — and now of namespaces — is to keep the universal infrastructure methods out of the user's namespace.

Every Caspian object derives from puck.uno/object, which provides a substantial set of universal operations: tri-value truthiness (bool, truthy?, null?, defined?), identity, role introspection, classes-stack inspection, the close-handler mechanism, and more. If those methods sat directly on every object, a trivial user class with one defined method:

caspian
class
    function &send($msg)
        ...
    end
end

would expose fifteen-plus methods at the top level — only one of which the user wrote. Auto-completion clutters; the namespace of "what this thing does" drowns in inherited infrastructure.

The fix: put all of the universal operations under a single namespace, conventionally object. So $conn exposes exactly two names at its top level — .send (what the user wrote) and .object (the universal-method namespace). The universal cloud is reachable but namespaced away.

This is the load-bearing reason the mechanism exists. Method-namespacing for user classes (like a metrics or transport cluster) is the same machinery applied to user-driven organization, but the original — and ongoing — purpose is keeping base-class methods from polluting every class's top-level surface.

.object is a namespace GitHub issue

caspian
$foo.object.bool          # tri-value truthiness
$foo.object.truthy?       # bool predicate
$foo.object.null?         # null predicate
$foo.object.classes       # the platter stack
$foo.object.role          # current role
$foo.object.on_close ...  # close-handler introspection

Under the new model, $foo.object is a name prefix into the object namespace. The dispatcher walks the path object.<method> and finds the method definition; the method runs with self = $foo. Identical to how any user-defined namespace would work.

What's gained vs the helper-sub-object model GitHub issue

What's given up GitHub issue

JSON shape GitHub issue

The class's methods block is a nested hash. The "nested" key on a hash value is the discriminator that distinguishes "namespace" from "method definition":

json
"methods": {
    "send": {
        "description": "...",
        "params": {...},
        "body": "..."
    },
    "beverage": {
        "nested": {
            "tea_earl_grey_hot": {"description": "...", "params": {...}, "body": "..."},
            "coffee_black": {"description": "...", "params": {...}, "body": "..."}
        }
    }
}

This is the shape already showing up in worldlets/worldlet.json on the officer class.

Dispatch mechanism GitHub issue

For a call like $foo.bar.gup(args):

  1. Walk $foo's class stack (top-down, per the method dispatch rule) looking for a class that has the path bar.gup defined.
  2. Within each class, traverse methods.bar.nested.gup. If found, that's the method.
  3. If a class has bar declared but it's a method (not a namespace), the path doesn't match — keep walking the class stack.
  4. First class with a matching path wins. If the walk completes without a match, raise method-not-found.
  5. Run the method with self = $foo. Full access to @bucket, %bucket, sibling methods, anything else self exposes.

The visited-set / inheritance-chain walking inside each platter applies as usual.

Worked example GitHub issue

vibecode
{"vibecode": {
    "section": "worked_example",
    "role": "concrete reference — the officer class in worldlet.json uses both a namespaced method group (beverage) and a top-level method (salute); spells out the call shapes that result and what the example does not yet exercise",
    "source": "documentation/requirements/ecoverse/worldlets/worldlet.json record b"
}}

The starfleet.com/officer class definition in worldlets/worldlet.json (record b) shows the shape in practice. Its methods block, simplified for readability:

json
"methods": {
    "beverage": {
        "nested": {
            "tea_earl_grey_hot": {
                "params": {"variety": {...}, "temperature": {...}},
                "body": "..."
            },
            "coffee_black": {
                "params": {"style": {...}, "temperature": {...}},
                "body": "..."
            }
        }
    },

    "salute": {
        "params": {"name": {...}, "rank": {...}, "attention": {...}, "style": {...}, "props": {...}},
        "body": "..."
    }
}

Two shapes side by side:

Resulting call shapes on an officer instance:

caspian
$picard.salute(name: 'Riker', rank: 'Commander')
$picard.beverage.tea_earl_grey_hot(variety: 'earl_grey', temperature: 'hot')
$picard.beverage.coffee_black(style: 'black', temperature: 'hot')

Each call dispatches per the Dispatch mechanism rule: walk the officer's class stack, look for the named path under methods, run with self = $picard.

What this example does NOT yet exercise, but the spec supports:

Adding any of those to worldlet.json would extend the example as the spec settles.

Open questions GitHub issue


© 2026 Puck.uno