NAME
Chorus::Frame - Frame-based knowledge representation with inheritance and procedural attachments.
VERSION
2.01
SYNOPSIS
use Chorus::Frame;
my $f1 = Chorus::Frame->new(
color => { _DEFAULT => 'blue' },
);
my $f2 = Chorus::Frame->new(
_ISA => $f1,
label => sub { 'I am ' . $SELF->color }, # $SELF = frame calling get()
);
print $f2->color; # 'blue' (inherited _DEFAULT)
print $f2->label; # 'I am blue'
# Inheritance mode affects how _VALUE/_DEFAULT/_NEEDED are resolved
Chorus::Frame::setMode(GET => 'Z');
Chorus::Frame::setMode(GET => 'N'); # back to default
# Select frames by slot (uses internal registry for fast lookup)
my @colored = fmatch(slot => 'color');
my @both = fmatch(slot => ['color', 'score']);
# Select the best-matching prototype frame given observed properties
my $proto = fselect(color => 'blue', can_fly => 1); # highest-scoring prototype
my @all = fselect(color => 'blue', _all => 1); # all candidates, ranked
# Frame networks: restrict fselect to a prototype and its declared alternatives
my $Bird = Chorus::Frame->new(can_fly => 1, legs => 2, _ALTERNATIVES => [$Bat]);
my $match = fselect(can_fly => 1, _alternatives => $Bird);
# Terminal slots: declare which slots must hold real data for a frame to be complete
my $proto = Chorus::Frame->new(_TERMINAL_SLOTS => ['color', 'size']);
my $inst = Chorus::Frame->new(_ISA => $proto, color => 'blue', size => 'large');
$inst->complete; # 1
# _ON_DELETE hook (if-removed demon, completes the Minsky triad)
my $f = Chorus::Frame->new(
tag => 'active',
_ON_DELETE => sub { print "Slot '${\$_[0]}' removed from frame\n" },
);
$f->delete('tag'); # → prints "Slot 'tag' removed from frame"
DESCRIPTION
A frame is a Perl hash blessed into Chorus::Frame. Its entries are called slots.
Key features:
Inheritance via the
_ISAslot (single frame or arrayref of frames).Procedural slots -- any slot value may be a
sub {}, evaluated lazily on access. The variable$SELFholds the frame on whichget()was originally called, making it available inside coderefs.Target-information slots --
_VALUE,_DEFAULTand_NEEDEDare tested in that order to resolve the value of a frame.Lifecycle hooks --
_BEFORE,_AFTERand_REQUIREintercept writes.Global registry -- every frame is registered automatically;
fmatch()uses this registry for O(1) slot-based lookups.
Special slots
The following names are reserved. Never use them as application slot names (except _ISA and _NOFRAME):
_KEY Unique MD5 key assigned at construction. Never set manually.
_PARENT_KEY Tracks sub-frame ownership for Copy-on-Write inside set().
_ISA Inheritance: a single frame or an arrayref of frames.
_VALUE Primary target information of this frame.
_DEFAULT Fallback when _VALUE is absent.
_NEEDED Last-resort coderef called when both _VALUE and _DEFAULT are absent
(backward chaining).
_BEFORE Hook called before a slot value changes.
_AFTER Hook called after a slot value changes (forward propagation).
_ON_DELETE Hook called after a slot is deleted; receives the slot name
(if-removed demon, completing the Minsky triad).
_REQUIRE Validation hook: return REQUIRE_FAILED to block the write.
_NOFRAME Prevents automatic promotion of a nested hash to a frame.
_TERMINAL_SLOTS Arrayref of slot names that must hold a real (_VALUE) value
for the frame to be considered complete (see complete()).
_ALTERNATIVES Arrayref of sibling prototype frames used by fselect()
as an alternative candidate pool (Minsky frame network).
Inheritance modes N and Z
The global mode controls how get() walks the inheritance chain.
Mode N (default) -- each valuation key is scanned across the whole inheritance tree before the next key is tried:
_VALUE on (frame, frame._ISA, frame._ISA._ISA, ...)
_DEFAULT on the same tree
_NEEDED on the same tree
Mode Z -- the full sequence (_VALUE, _DEFAULT, _NEEDED) is tried on each frame before descending to its parents:
frame : _VALUE, _DEFAULT, _NEEDED
frame._ISA: _VALUE, _DEFAULT, _NEEDED ...
Switch with Chorus::Frame::setMode(GET => 'Z') or setMode(GET => 'N').
Exports
Chorus::Frame exports by default:
$SELF Current frame context, updated by get() and set().
&fmatch Slot-based frame selection function.
&fselect Prototype selection by scored slot/value matching (Minsky-style).
&setMode Switches the inheritance mode (N or Z).
REQUIRE_FAILED Constant (-1) returned by _REQUIRE to abort a write.
FUNCTIONS
setMode
Sets the global inheritance mode used by get() to resolve target information (_VALUE, _DEFAULT, _NEEDED).
Chorus::Frame::setMode(GET => 'N'); # Mode N -- default
Chorus::Frame::setMode(GET => 'Z'); # Mode Z
Chorus::Frame::setMode('N'); # short form
See "Inheritance modes N and Z" in the DESCRIPTION for a detailed comparison.
METHODS
_keys
Returns the slot names of the frame, excluding the internal keys _KEY and _PARENT_KEY.
my @slots = $frame->_keys;
Use this instead of keys %$frame when you need to iterate over application slots without exposing internal bookkeeping entries.
_push
Appends one or more elements to a slot, converting it to an arrayref when necessary.
$frame->_push('tags', 'red', 'big');
_inherits
Adds one or more parent frames to the inheritance chain outside the constructor. Each parent is added at most once; duplicates are silently ignored.
$frame->_inherits($parent1, $parent2);
Used internally by Chorus::Engine::new() to wire agent instances to the engine prototype.
new
Constructor. Converts a flat list of key/value pairs into a Chorus::Frame object.
my $f = Chorus::Frame->new(
slot_a => 'value',
slot_b => sub { 'computed: ' . $SELF->slot_a }, # procedural slot
nested => {
_ISA => $proto,
_NEEDED => sub { compute_default() },
},
);
All nested plain hashes are automatically and recursively promoted to Chorus::Frame, unless they contain the _NOFRAME flag. Every frame receives a unique _KEY and is registered in the global repository so that fmatch() can find it.
Do not set _KEY or _PARENT_KEY manually.
_reset
Clears all global registries (%FMAP, %REPOSITORY, %INSTANCES), resets the $SELF context stack and restores the inheritance mode to N.
Chorus::Frame::_reset();
For testing only. Call between test cases to guarantee frame isolation.
get
Returns the value associated with a space-separated slot path.
$frame->get('slot')
$frame->get('slot_a slot_b') # traverse slot_a, then resolve slot_b
$frame->slot_a # AUTOLOAD short form -- equivalent to get('slot_a')
The last step in the path is resolved through _VALUE, _DEFAULT and _NEEDED in that order (subject to the current "Inheritance modes N and Z").
get() pushes the current frame onto a stack and sets $SELF to it, so procedural slots can refer back to the calling frame:
my $f = Chorus::Frame->new(
name => 'Chorus',
label => sub { 'Module: ' . $SELF->name },
);
print $f->label; # "Module: Chorus"
The short form $f->slotname is equivalent to $f->get('slotname'). Arguments are forwarded when the resolved value is a coderef:
$f->slotname(@args); # calls get('slotname'), then invokes the result with @args
delete
Removes a slot and unregisters it from the global repository.
$frame->delete('slot');
$frame->delete('slot_a slot_b'); # deletes slot_b inside slot_a
Always use this method instead of delete $frame-{slot}>. Direct hash deletion bypasses the registry, causing fmatch() to return stale results for that slot.
After the slot is removed, the _ON_DELETE hook is invoked (if present), receiving the deleted slot name as its argument. This is the if-removed demon that completes the Minsky triad (_NEEDED / _AFTER / _ON_DELETE).
set
Associates a value to a slot (or slot path) and updates the global registry.
$frame->set('slot', $value);
$frame->set('slot_a slot_b', $value);
Always use this method instead of $frame->{slot} = $value. Direct hash assignment bypasses _registerSlot(), so fmatch(slot => 'slot') will not return this frame -- the slot exists on the hash but is invisible to the inference engine.
Lifecycle hooks -- when the terminal slot has them, they fire in order:
1. _REQUIRE is called with the new value.
Return REQUIRE_FAILED (-1) to abort the write.
2. _BEFORE is called with the new value.
3. The value is stored in _VALUE and registered.
4. _AFTER is called with the new value (forward chaining).
Copy-on-Write -- when traversing a sub-frame whose _PARENT_KEY differs from the current frame's _KEY, a shadow frame (_ISA => $shared) is created locally before writing. This prevents mutations from affecting shared structures.
my $f = Chorus::Frame->new(a => { b => { _VALUE => 'old' } });
$f->set('a b', 'new');
print $f->get('a b'); # "new"
fmatch
Returns all frames that provide the given slot(s), either directly or by inheritance, using the internal registry for efficient lookups.
my @frames = fmatch(slot => 'color'); # all frames having 'color'
my @frames = fmatch(slot => ['color', 'score']); # intersection
my @frames = fmatch(slot => 'color', from => \@list); # restricted space
my @high = grep { $_->score > 5 } fmatch(slot => 'score');
slot may be a scalar or an arrayref. Multiple slot names return only frames providing all of them. The optional from arrayref narrows the search to a known subset.
complete
Returns 1 if every slot listed in _TERMINAL_SLOTS (on this frame or inherited) holds a defined value via get(), undef otherwise.
my $proto = Chorus::Frame->new(_TERMINAL_SLOTS => ['color', 'size']);
my $inst = Chorus::Frame->new(_ISA => $proto, color => 'blue', size => 'large');
$inst->complete; # 1
my $partial = Chorus::Frame->new(_ISA => $proto, color => 'red');
$partial->complete; # undef (size not filled)
_TERMINAL_SLOTS is inherited: a child frame that does not redeclare it will use its parent's list. Each slot is resolved with get(), so procedural slots (sub {}) and _DEFAULT values count as filled.
Relationship to Minsky's model: terminal slots correspond to Minsky's terminal nodes -- positions in the frame that must be grounded in actual observed data for the frame to describe a real situation rather than a generic prototype.
fselect
Selects the best-matching frame(s) from the global registry given a set of observed slot/value pairs. This implements the frame-selection mechanism described by Minsky (1974): given a situation described by a set of properties, find the prototype that fits it best.
# Best single match (highest score)
my $proto = fselect(color => 'blue', can_fly => 1);
# All candidates with a positive score, ranked best-first
my @ranked = fselect(color => 'blue', can_fly => 1, _all => 1);
# Restrict the search space
my $proto = fselect(color => 'blue', _from => \@candidates);
# Frame network: search seed + its declared _ALTERNATIVES
my $Bird = Chorus::Frame->new(can_fly => 1, _ALTERNATIVES => [$Bat, $Insect]);
my $best = fselect(can_fly => 1, legs => 6, _alternatives => $Bird);
# Accept candidates with zero matching slots (score >= 0)
my @all = fselect(color => 'blue', _all => 1, _min => 0);
Scoring -- for each candidate frame, one point is awarded for each slot => value pair where the frame provides the slot and its resolved value (via get()) matches the observed value. Frames with a score strictly below _min (default: 1) are excluded.
Options (prefixed with _ to avoid collision with slot names):
_all If true, return all matching frames ranked by descending score
instead of just the best one. In list context, the return value
is a list of frames. In scalar context, it is an arrayref.
_from Arrayref -- restrict the candidate pool to this list of frames.
If absent, all registered frames are considered.
_alternatives Seed frame -- restrict the candidate pool to the seed frame plus
all frames listed in its C<_ALTERNATIVES> slot. This implements
Minsky's frame network: when a prototype does not fit, try its
declared siblings. Cannot be combined with C<_from>.
_min Minimum score to be included in the result (default: 1).
Pass C<_min =E<gt> 0> to include frames that match none of the
observed slots.
Returns undef (scalar) or () (list) when no candidate meets the minimum score.
AUTHOR
Christophe Ivorra
BUGS
Please report bugs via the CPAN request tracker: http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Chorus-Frame
SUPPORT
perldoc Chorus::Frame
AnnoCPAN -- http://annocpan.org/dist/Chorus-Frame
CPAN Ratings -- http://cpanratings.perl.org/d/Chorus-Frame
Search CPAN -- http://search.cpan.org/dist/Chorus-Frame/
SEE ALSO
Chorus::Engine, Chorus::Expert, Chorus::Collection::List, Chorus::Collection::Filter
LICENSE AND COPYRIGHT
Copyright 2013 Christophe Ivorra.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.