TER1 (Statement): 100.00%
TER2 (Branch): 96.67%
TER3 (LCSAJ): 100.0% (9/9)
Approximate LCSAJ segments: 61
โ Covered โ this LCSAJ path was executed during testing.
โ Not covered โ this LCSAJ path was never executed. These are the paths to focus on.
Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:
start โ end โ jump
Uncovered paths show [NOT COVERED] in the tooltip.
1: package Class::Simple::Cached; 2: 3: use strict; 4: use warnings; 5: use autodie qw(:all); 6: 7: use Carp (); 8: use Class::Simple; 9: use Params::Get 0.15; 10: use Scalar::Util (); 11: use Sub::Protected; 12: 13: # Stored in the cache to distinguish "the object returned undef" from 14: # "this key has never been cached". Must never be a legitimate return value. 15: use constant UNDEF_SENTINEL => __PACKAGE__ . '>UNDEF<'; 16: 17: # Evaluated once at compile time so DESTROY never pays a string comparison 18: # on every object teardown just to decide whether ${^GLOBAL_PHASE} exists. 19: use constant _GLOBAL_PHASE_AVAILABLE => defined($^V) && ($^V ge 'v5.14.0'); 20: 21: =head1 NAME 22: 23: Class::Simple::Cached - cache getter results for any get/set object 24: 25: =head1 VERSION 26: 27: Version 0.06 28: 29: =cut 30: 31: our $VERSION = '0.06'; 32: 33: =encoding UTF-8 34: 35: =head1 SYNOPSIS 36: 37: use CHI; 38: use Class::Simple::Cached; 39: 40: # Wrap an existing object with a cache layer 41: my $cache = CHI->new(driver => 'RawMemory', global => 1); 42: my $obj = Class::Simple::Cached->new( 43: cache => $cache, 44: object => My::Expensive::Object->new(), 45: ); 46: 47: $obj->name('Alice'); # setter: delegates to wrapped object, updates cache 48: my $n = $obj->name(); # getter: returns cached 'Alice' without hitting object 49: 50: # Or use a plain hash ref as the cache backend 51: my %store; 52: my $simple = Class::Simple::Cached->new(cache => \%store); 53: $simple->colour('blue'); 54: print $simple->colour(); # 'blue', served from %store 55: 56: =head1 DESCRIPTION 57: 58: A subclass of L<Class::Simple> that transparently caches the return values of 59: getter calls, so that repeated reads of expensive-to-compute or 60: expensive-to-transport values hit the cache instead of the wrapped object. 61: 62: Cache coherency is I<not> automatic. If the wrapped object's state changes 63: through a path other than the cached wrapper, callers must invalidate the cache 64: themselves. 65: 66: =head1 SUBROUTINES/METHODS 67: 68: =head2 new 69: 70: Constructs a C<Class::Simple::Cached> instance that wraps C<object> behind 71: C<cache>. 72: 73: =head3 ARGUMENTS 74: 75: =over 4 76: 77: =item C<cache> (mandatory) 78: 79: Either a blessed object implementing C<get($key)>, C<set($key, $val, $expires)>, 80: and C<purge()> (e.g. any L<CHI> driver); or a plain hash reference used as an 81: in-process store. 82: 83: =item C<object> (optional) 84: 85: The object whose methods will be proxied and cached. Defaults to a fresh 86: C<Class::Simple> instance. 87: 88: =back 89: 90: Calling C<< ->new() >> on an already-blessed instance returns a shallow clone 91: (all stored fields are merged, including the existing cache handle). 92: 93: =head3 RETURNS 94: 95: A blessed C<Class::Simple::Cached> instance. 96: 97: =head3 SIDE EFFECTS 98: 99: None beyond allocating the new instance. 100: 101: =head3 EXAMPLE 102: 103: # CHI-backed cache 104: use CHI; 105: my $obj = Class::Simple::Cached->new( 106: cache => CHI->new(driver => 'RawMemory', global => 1), 107: object => My::Model->new(), 108: ); 109: 110: # Hash-ref cache (useful for tests or short-lived objects) 111: my $obj = Class::Simple::Cached->new(cache => {}); 112: 113: # Clone an existing wrapped object 114: my $clone = $obj->new(); 115: 116: =head3 API SPECIFICATION 117: 118: Input: 119: cache => HashRef | CHICompatibleObject # required 120: object => Object # optional 121: 122: Output: 123: Class::Simple::Cached instance 124: 125: =head3 MESSAGES 126: 127: Message Meaning Resolution 128: --------------------------------------------------- ----------------------------------- ---------------------------------------- 129: "use ->new() not ::new() to instantiate" Called as Class::Simple::Cached::new Use $obj->new() or ClassName->new() 130: "Usage: $class->new(cache => \$cache)" No arguments supplied Pass at least cache => ... 131: "Cache must be ref to HASH or object" cache is a plain scalar or wrong ref Use a hashref or CHI-compatible object 132: "Cache object must implement get, set, purge" Blessed cache lacks required methods Use a fully CHI-compatible object 133: 134: =head3 PSEUDOCODE 135: 136: new(class, args): 137: IF class undefined â carp and return undef 138: IF class is blessed â merge fields, return shallow clone 139: IF no args â croak Usage message 140: PARSE args into hashref via Params::Get 141: IF params.object absent â params.object = Class::Simple->new() 142: IF params.cache is a blessed object: 143: VERIFY it can('get') AND can('set') AND can('purge') 144: IF not â croak capability message 145: RETURN bless params, class 146: IF params.cache is a HASH ref: 147: RETURN bless params, class 148: croak "Cache must be ref to HASH or object" 149: 150: =cut 151: 152: sub new 153: { โ154 โ 157 โ 163 154: my $class = shift; 155: 156: # Guard: always call as a method, not a bare function 157: if(!defined($class)) {Mutants (Total: 1, Killed: 1, Survived: 0)
158: Carp::carp(__PACKAGE__, ' use ->new() not ::new() to instantiate'); 159: return; 160: } 161: 162: # When called on a blessed instance, return a shallow clone โ163 โ 163 โ 192 163: if(Scalar::Util::blessed($class)) {
Mutants (Total: 1, Killed: 1, Survived: 0)
164: my $params = Params::Get::get_params(undef, \@_) || {}; 165: my %merged = (%{$class}, %{$params}); 166: # Always recalculate internal dispatch fields after the merge so that 167: # caller-supplied _is_hash_cache or _cache_prefix args cannot corrupt 168: # the cache-type flag or the key-prefix (internal field injection). 169: $merged{'_is_hash_cache'} = ref($merged{'cache'}) eq 'HASH'; 170: $merged{'_cache_prefix'} = ref($class) . ':'; 171:
Mutants (Total: 2, Killed: 2, Survived: 0)
172: # Validate the (potentially-replaced) cache object using the same rules 173: # as new() â the clone path must not produce an unusable object. 174: if(!$merged{'_is_hash_cache'}) { 175: if(Scalar::Util::blessed($merged{'cache'})) { 176: unless($merged{'cache'}->can('get')
Mutants (Total: 2, Killed: 2, Survived: 0)
177: && $merged{'cache'}->can('set') 178: && $merged{'cache'}->can('purge')) 179: { 180: Carp::croak("Cache object must implement 'get', 'set', and 'purge' methods"); 181: } 182: } else { 183: Carp::croak(ref($class) . ': Cache must be ref to HASH or object'); 184: } 185: } 186: 187: return bless \%merged, ref($class); 188: } 189: 190: # Require at least one argument so Params::Get's confess is never reached;
Mutants (Total: 1, Killed: 1, Survived: 0)
191: # we want croak (Test::Carp-compatible) not confess. โ192 โ 192 โ 196 192: if(scalar(@_) == 0) {
Mutants (Total: 1, Killed: 1, Survived: 0)
193: Carp::croak('Usage: ', $class, '->new(cache => $cache)'); 194: } 195: โ196 โ 206 โ 219 196: my $params = Params::Get::get_params('cache', @_) || {}; 197: 198: # Default the wrapped object to a bare Class::Simple instance
Mutants (Total: 2, Killed: 2, Survived: 0)
199: $params->{'object'} ||= Class::Simple->new(); 200: 201: # Precompute per-instance fields so AUTOLOAD (hot path) avoids ref() and 202: # string concatenation on every call. 203: $params->{'_is_hash_cache'} = ref($params->{'cache'}) eq 'HASH';
Mutants (Total: 1, Killed: 1, Survived: 0)
204: $params->{'_cache_prefix'} = "$class:";
Mutants (Total: 2, Killed: 2, Survived: 0)
205: 206: if(Scalar::Util::blessed($params->{'cache'})) { 207: # Verify the cache object speaks the required interface 208: unless($params->{'cache'}->can('get') 209: && $params->{'cache'}->can('set') 210: && $params->{'cache'}->can('purge')) 211: { 212: Carp::croak("Cache object must implement 'get', 'set', and 'purge' methods"); 213: } 214: return bless $params, $class; 215: } 216: 217: # Transitive reduction: _is_hash_cache already holds ref($cache) eq 'HASH' 218: # computed two statements earlier â no need to call ref() again. โ219 โ 219 โ 223 219: if($params->{'_is_hash_cache'}) { 220: return bless $params, $class; 221: } 222: 223: Carp::croak("$class: Cache must be ref to HASH or object"); 224: } 225: 226: =head2 can 227: 228: Reports whether this wrapper (or its embedded object) can handle a method. 229: 230: =head3 ARGUMENTS 231: 232: =over 4 233: 234: =item C<$method> â the method name to probe. 235: 236: =back 237: 238: =head3 RETURNS 239: 240: True if the method is known; false otherwise. 241: 242: =head3 EXAMPLE 243: 244: $obj->can('name'); # true if the wrapped object has a name() method 245: 246: =head3 API SPECIFICATION 247: 248: Input: method_name : Str
Mutants (Total: 2, Killed: 2, Survived: 0)
249: Output: Bool 250: 251: =head3 MESSAGES 252: 253: None. 254: 255: =cut 256: 257: sub can 258: { 259: my ($self, $method) = @_; 260: 261: # Undefined method name is outside the documented API; return undef cleanly. 262: return unless defined $method; 263: 264: # When called as a class method there is no wrapped object to probe 265: return $self->SUPER::can($method) unless Scalar::Util::blessed($self); 266: 267: return ($method eq 'new') 268: || $self->{'object'}->can($method) 269: || $self->SUPER::can($method); 270: } 271: 272: =head2 isa 273: 274: Reports whether this wrapper or its embedded object is of a given class. 275: 276: =head3 ARGUMENTS 277: 278: =over 4 279: 280: =item C<$class> â the class name to test. 281: 282: =back 283: 284: =head3 RETURNS 285: 286: True if the wrapper or the wrapped object is-a C<$class>. 287: 288: =head3 EXAMPLE 289:
290: $obj->isa('My::Model'); # delegates to the wrapped object 291:Mutants (Total: 2, Killed: 0, Survived: 2)
- BOOL_NEGATE_289_2: Negate boolean return expression
MEDIUM: Add tests asserting both true and false outcomes๐งช Suggested Test# Boolean branch test suggestion ok( !func(INPUT), 'Verify boolean branch behaviour' );- RETURN_UNDEF_289_2: Replace return expression with undef
LOW: Mutation survived, but impact may be minor๐งช Suggested Test# Return value assertion is( func(INPUT), EXPECTED, 'Verify correct return value' );Mutants (Total: 2, Killed: 2, Survived: 0)
292: =head3 API SPECIFICATION 293: 294: Input: class_name : Str 295: Output: Bool
Mutants (Total: 2, Killed: 2, Survived: 0)
296: 297: =head3 MESSAGES 298: 299: None. 300: 301: =cut 302: 303: sub isa 304: { 305: my ($self, $class) = @_; 306: 307: # Undefined class name is outside the documented API; return undef cleanly. 308: return unless defined $class; 309: 310: # When called as a class method there is no wrapped object to interrogate 311: return $self->SUPER::isa($class) unless Scalar::Util::blessed($self); 312: 313: return 1 if $class eq ref($self) 314: || $class eq __PACKAGE__
Mutants (Total: 1, Killed: 1, Survived: 0)
315: || $self->SUPER::isa($class); 316: 317: return $self->{'object'}->isa($class);
Mutants (Total: 1, Killed: 1, Survived: 0)
318: } 319: 320: # DESTROY â purge this instance's entries from the cache on object teardown. 321: # 322: # Purpose: Prevent stale entries from leaking into a shared cache after 323: # the wrapper goes out of scope. 324: # Entry: Called automatically by Perl's garbage collector. 325: # Exit Status: None (void). 326: # Side Effects: Removes all cache keys prefixed with ref($self) for hash caches; 327: # calls purge() for CHI-style caches. 328: # See https://github.com/Perl/perl5/issues/14673 for why an 329: # explicit DESTROY is required even though AUTOLOAD could catch it. 330: sub DESTROY 331: { โ332 โ 336 โ 344 332: my $self = shift; 333:
Mutants (Total: 2, Killed: 2, Survived: 0)
334: my $cache = $self->{'cache'} or return; 335: 336: if($self->{'_is_hash_cache'}) { 337: # Remove only this class's keys to avoid stomping on siblings sharing the hash 338: my $prefix = $self->{'_cache_prefix'}; 339: delete $cache->{$_} for grep { index($_, $prefix) == 0 } keys %{$cache}; 340: return;
Mutants (Total: 1, Killed: 1, Survived: 0)
341: } 342: 343: # Skip purge during global destruction to avoid order-of-destruction crashes 344: return if _GLOBAL_PHASE_AVAILABLE && ${^GLOBAL_PHASE} eq 'DESTRUCT'; 345: 346: $cache->purge(); 347: } 348: 349: # _cache_get / _cache_set â unified read/write; hides hash-ref vs. CHI dispatch 350: # so the rest of the code never needs to branch on ref($cache). 351: sub _cache_get :Protected 352: { 353: my ($self, $key) = @_; 354: my $cache = $self->{'cache'}; 355: return $self->{'_is_hash_cache'} ? $cache->{$key} : $cache->get($key); 356: } 357: 358: sub _cache_set :Protected 359: { โ360 โ 362 โ 0 360: my ($self, $key, $val) = @_; 361: my $cache = $self->{'cache'}; 362: if($self->{'_is_hash_cache'}) { 363: $cache->{$key} = $val; 364: } else { 365: $cache->set($key, $val, 'never'); 366: } 367: } 368: 369: =head2 AUTOLOAD (getter/setter proxy) 370: 371: Intercepts every method call that is not explicitly defined, proxying it to 372: the wrapped object with a caching layer for zero-argument (getter) calls. 373: 374: Setter calls (one or more arguments) always pass through to the wrapped object 375: and update the cache with the new value. 376: 377: =head3 ARGUMENTS 378: 379: $method() â getter: returns cached value if present, else calls object 380: $method($scalar) â scalar setter: stores scalar, updates cache 381: $method(@list) â array setter: stores list, updates cache 382: 383: =head3 RETURNS 384: 385: The value returned by the wrapped object (or the cached copy thereof). 386: 387: =head3 SIDE EFFECTS 388: 389: =over 4 390: 391: =item * Getter: may write to the cache on first call. 392: 393: =item * Setter: writes to both the wrapped object and the cache. 394: 395: =item * DESTROY: clears cache entries for this instance (see above). 396: 397: =back 398: 399: =head3 EXAMPLE 400: 401: $obj->colour('red'); # setter â writes 'red' to object and cache 402: $obj->colour(); # getter â returns 'red' from cache 403: 404: =head3 API SPECIFICATION 405: 406: Input (getter): method_name : Str, args : () 407: Input (setter): method_name : Str, args : (Scalar | List) 408: Output: the stored/retrieved value or list 409: 410: =head3 MESSAGES 411: 412: Message Meaning Resolution 413: -------------------------------- ---------------------------------- ---------------------------------------- 414: "$method" (croak) Cached array's first element is Do not store the sentinel string 415: the UNDEF_SENTINEL string as a real value in the wrapped object 416: 417: =head3 PSEUDOCODE 418: 419: AUTOLOAD(method, args...): 420: key = ref(self) + ":" + method 421: 422: IF no args (getter mode): 423: val = cache_get(key) 424: IF cache hit: 425: IF val is a plain string (not a ref): 426: IF val is UNDEF_SENTINEL â return undef 427: RETURN val 428: IF val is an arrayref: 429: IF first element is a plain string AND equals UNDEF_SENTINEL â croak 430: RETURN dereferenced list 431: RETURN val (blessed object) 432: # Cache miss â ask the wrapped object 433: IF list context: 434: result_list = object->method() 435: IF empty â return () 436: cache_set(key, \result_list) 437: RETURN result_list 438: # Scalar context 439: result = object->method() 440: IF defined: 441: cache_set(key, result) 442: RETURN result 443: cache_set(key, UNDEF_SENTINEL) 444: RETURN undef 445: 446: ELSE (setter mode): 447: IF more than one arg (array setter): 448: val = object->method(\@args) # wrapped object stores arrayref 449: IF defined: 450: cache_set(key, val) 451: RETURN @val 452: cache_set(key, UNDEF_SENTINEL)
Mutants (Total: 2, Killed: 2, Survived: 0)
453: RETURN undef 454: ELSE (scalar setter): 455: val = object->method(args[0]) 456: cache_set(key, val // UNDEF_SENTINEL) 457: RETURN val 458:
Mutants (Total: 1, Killed: 1, Survived: 0)
459: =cut 460: 461: sub AUTOLOAD 462: { โ463 โ 474 โ 516 463: our $AUTOLOAD;
Mutants (Total: 1, Killed: 1, Survived: 0)
464: # rindex+substr avoids regex engine startup on every method dispatch 465: my $param = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2); 466:
Mutants (Total: 2, Killed: 2, Survived: 0)
467: my $self = shift; 468: # _cache_prefix is precomputed at new() time (= ref($self) . ":") to avoid 469: # calling ref() and doing string concat on every AUTOLOAD invocation. 470: my $key = $self->{'_cache_prefix'} . $param; 471: my $object = $self->{'object'}; 472:
Mutants (Total: 2, Killed: 2, Survived: 0)
473: # Getter path âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 474: if(scalar(@_) == 0) { 475: my $rc = $self->_cache_get($key); 476:
Mutants (Total: 2, Killed: 2, Survived: 0)
477: # Truthiness check: falsy-but-defined values (0, '') are treated as cache 478: # misses so the object is re-invoked every call. This is a known 479: # limitation; see the LIMITATIONS section in the POD. 480: if($rc) {
Mutants (Total: 1, Killed: 1, Survived: 0)
481: # Premise 1: $rc is truthy (the if-guard guarantees this). 482: # Premise 2: ref($rc) returns '' for plain scalars, 'ARRAY' for 483: # arrayrefs, or a package name for blessed objects. 484: # The three branches below are mutually exclusive and exhaustive.
Mutants (Total: 2, Killed: 2, Survived: 0)
485: if(!ref($rc)) { 486: # Conclusion: plain scalar â only type that can equal the sentinel. 487: return if $rc eq UNDEF_SENTINEL; 488: return $rc; 489: } elsif(ref($rc) eq 'ARRAY') { 490: # Premise: !ref($rc) was false, so ref($rc) is truthy; no need
Mutants (Total: 2, Killed: 2, Survived: 0)
491: # to re-test ref() â the elsif is the deductive next step. 492: Carp::croak($param) 493: if !ref($rc->[0]) && $rc->[0] eq UNDEF_SENTINEL; 494: return @{$rc};
Mutants (Total: 4, Killed: 4, Survived: 0)
495: } 496: # Conclusion: ref($rc) is truthy and â 'ARRAY' â blessed object. 497: # Overloaded eq was never invoked; return the object as-is. 498: return $rc;
Mutants (Total: 2, Killed: 2, Survived: 0)
499: } 500: 501: # Cache miss â call the real object 502: if(wantarray) { 503: my @result = $object->$param(); 504: return unless scalar(@result); # empty list: don't cache, return () 505: $self->_cache_set($key, \@result); 506: return @result;
Mutants (Total: 2, Killed: 2, Survived: 0)
507: } 508: 509: # Scalar / void context: cache the value (or the sentinel for undef) 510: my $val = $object->$param(); 511: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 512: return $val; 513: } 514: 515: # Setter path â array âââââââââââââââââââââââââââââââââââââââââââââââââââââ โ516 โ 516 โ 526 516: if(scalar(@_) > 1) { 517: # Pass the list as an arrayref; the wrapped object stores it 518: my $val = $object->$param(\@_); 519: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 520: return defined($val) ? @{$val} : (); 521: } 522: 523: # Setter path â scalar ââââââââââââââââââââââââââââââââââââââââââââââââââââ 524: # CHI's set() returns the cache object, not the stored value; 525: # capture the value before the set call and return it directly. 526: my $val = $object->$param($_[0]); 527: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 528: return $val; 529: } 530: 531: =head1 LIMITATIONS 532: 533: =over 4 534: 535: =item Setter arguments are not part of the cache key. 536: 537: Calls like C<< $obj->method($arg) >> use the key C<ClassName:method> regardless 538: of C<$arg>. Setters with different arguments therefore overwrite each other's 539: cache entries. For read-only caching of parameterised methods, see 540: L<Class::Simple::Readonly::Cached>. 541: 542: =item Falsy scalar return values are not cached. 543: 544: Methods that return C<0> or C<''> (false but defined) are treated as a cache 545: miss on every call: the underlying object is re-invoked each time. Only 546: C<undef> is cached (via the UNDEF_SENTINEL). If you need to cache C<0>, wrap 547: it in a container object or use a different caching strategy. 548: 549: =item Sentinel collision. 550: 551: If the wrapped object ever returns the literal string 552: C<< "Class::Simple::Cached\>UNDEF\<" >> as a scalar, or as the first element of a 553: list, the module will misinterpret it as a cached-undef marker. This string is 554: deliberately unusual, but callers working with arbitrary string data should be 555: aware of the constraint. 556: 557: =item Shared cache and multiple classes. 558: 559: When two C<Class::Simple::Cached> instances of B<different> classes share the 560: same cache object, their keys are namespaced by class name and do not collide. 561: However, C<purge()> on a CHI-style cache is global: destroying one instance will 562: purge I<all> entries from a shared CHI cache, including those of the other 563: instance. Use per-instance cache objects, or a hash-ref cache, to avoid this. 564: 565: =item Does not work with L<Memoize>. 566: 567: =item Overloaded C<eq> on cached values. 568: 569: If the wrapped object returns a blessed value that overloads the C<eq> operator, 570: the sentinel comparison in the getter uses a C<ref()> pre-check so that only 571: plain strings are compared against the sentinel. Array elements are subject to 572: the same pre-check. Callers wrapping objects that return sentinel-like strings 573: via overloading should be aware of this guard. 574: 575: =back 576: 577: =head1 AUTHOR 578: 579: Nigel Horne, C<< <njh at nigelhorne.com> >> 580: 581: =head1 BUGS 582: 583: Please report bugs and feature requests at 584: L<https://github.com/nigelhorne/Class-Simple-Cached/issues>. 585: 586: =head1 SEE ALSO 587: 588: L<Class::Simple>, L<CHI>, L<Class::Simple::Readonly::Cached> 589: 590: =over 4 591: 592: =item * L<Test Dashboard|https://nigelhorne.github.io/Class-Simple-Cached/coverage/> 593: 594: =back 595: 596: =head1 SUPPORT 597: 598: This module is provided as-is without any warranty. 599: 600: You can find documentation for this module with the perldoc command: 601: 602: perldoc Class::Simple::Cached 603: 604: =over 4 605: 606: =item * MetaCPAN: L<https://metacpan.org/release/Class-Simple-Cached> 607: 608: =item * Source: L<https://github.com/nigelhorne/Class-Simple-Cached> 609: 610: =item * CPANTS: L<http://cpants.cpanauthors.org/dist/Class-Simple-Cached> 611: 612: =item * Testers Matrix: L<http://matrix.cpantesters.org/?dist=Class-Simple-Cached> 613: 614: =back 615: 616: =head1 FORMAL SPECIFICATION 617: 618: =head2 new 619: 620: âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 621: [State] 622: cache : â(HashRef ⪠CHIObject) 623: object : Object 624: 625: [CHIObject] 626: can_get : Method 627: can_set : Method 628: can_purge : Method 629: 630: new ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 631: Î(cache, object) 632: cache? : HashRef ⪠CHIObject 633: object? : Object ⪠{â } 634: âââââââââââââââââââââââââââââââââââââââââââââââââ 635: cache? â â 636: cache â² = cache? 637: object â² = (object? â â â¹ object?) ⨠Class::Simple.new() 638: âââââââââââââââââââââââââââââââââââââââââââââââââ 639: 640: =head2 can 641: 642: can ââââââââââââââââââââââââââââââââââââââââââââââââââ 643: Î(cache, object) 644: method? : MethodName 645: âââââââââââââââââââââââââââââââââââââââââââââââââ 646: result! = (method? = 'new') 647: ⨠object.can(method?) 648: ⨠SUPER::can(method?) 649: 650: =head2 isa 651: 652: isa ââââââââââââââââââââââââââââââââââââââââââââââââââ 653: Î(cache, object) 654: class? : ClassName 655: âââââââââââââââââââââââââââââââââââââââââââââââââ 656: result! = (class? = ref(self)) 657: ⨠(class? = __PACKAGE__) 658: ⨠SUPER::isa(class?) 659: ⨠object.isa(class?) 660: 661: =head2 AUTOLOAD 662: 663: AUTOLOAD âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 664: Î(cache) 665: method? : MethodName 666: args? : Seq(Any) 667: âââââââââââââââââââââââââââââââââââââââââââââââââ 668: key = ref(self) â ":" â method? 669: 670: Getter (args? = â ): 671: (â v ⢠cache_hit(key, v) â§ v â UNDEF_SENTINEL â¹ result! = v) 672: ⨠(cache_hit(key, UNDEF_SENTINEL) â¹ result! = undef) 673: ⨠(¬cache_hit(key, _) 674: â§ result! = object.method?() 675: â§ cacheâ² = cache ⪠{key ⦠encode(result!)}) 676: 677: Setter (args? â â ): 678: objectâ².method?(args?) = args? 679: cacheâ² = cache ⪠{key ⦠encode(objectâ².method?())} 680: result! = args? 681: 682: =head1 LICENCE AND COPYRIGHT 683: 684: Copyright (C) 2019-2026, Nigel Horne 685: 686: Usage is subject to the GPL2 licence terms. 687: If you use it, 688: please let me know. 689: 690: =cut 691: 692: 1;