File Coverage

blib/lib/Object/Hybrid/Class.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             package Object::Hybrid::Class;
2 1     1   589 use Class::Tag qw(tagger_class);
  1         2  
  1         4  
3 1     1   6 use Object::Hybrid::Class qw(is mutable_class);
  1         1  
  1         6  
4            
5             1;
6            
7             =head1 DESCRIPTION
8            
9             This document describes requirements for custom hybrid classes. It may be useful only for developers, not users of Hybrid::Object, as currently Hybrid::Object by default automatically constructs hybrid class from any given class and use it to promote() primitives, so that there is no need to be concerned with the following requirements. However, this document is still useful to understand Object::Hybrid architecture.
10            
11             NOTE: this document may be outdated in some places, but still outlines architecture correctly.
12            
13             =head1 Hybrid classes
14            
15             Primitives can be promote()d by blessing it into custom hybrid class specified as argument. There are few advantages of using custom hybrid classes, in particular - opportunity for operator overloading.
16            
17             Custom hybrid class is required to subclass Object::Hybrid::CLASS (the default hybrid class) and is to meet certain other requirements, but all those requirements are met automatically by subclassing Object::Hybrid::CLASS, which makes construction of custom hybrid classes easier. For example, custom subclass of Object::Hybrid::CLASS may just define overloading of some operators, etc.
18            
19             =begin comment
20            
21             Custom hybrid class may in theory target only specific type of primitives, as well as either tied() or not tied(), but then it is the responsibility of caller to use it conditionally only for promote()ing primitives of corresponding type. In contrast, default Object::Hybrid::CLASS is universal hybrid class that works fine for all supported types of primitives.
22            
23             =end comment
24            
25             Although subclassing Object::Hybrid::CLASS allows to avoid building it from scratch, still hybrid class requirements need to be understood for extending Object::Hybrid::CLASS in subclasses.
26            
27             Promoting primitive to become hybrid (i.e. bless()ing it into hybrid class) simply adds object interface to primitive and is nothing more than a way to extend Perl primitives. There are many ways hybrid class can be designed. However, designing hybrid class in a special way allows to achieve compatibility and synergy with another major way of extending primitives - perltie API, as well as some other goals.
28            
29             Because of this, hybrid class is required to meet specific set of requirements. As a result of these requirements, which narrow design of hybrid class/interface, hybrids are useful not only as simple syntactic sugar, but become compatible with and complementary to using perltie API/classes for extending primitives.
30            
31             =begin comment
32            
33             Essentially, perltie API allows to extend primitives in two related ways: 1) it allows to "pack" non-standard behavior into standard Perl primitives; and at the same time 2) allows to extend tied() objects beyond perltie interface. The first way is nearly perfect (except for gaps in perltie API implementation), but second is far from perfect as accessing extended interface of tied() object requires (conditional) tied() calls and that complicates code that is to manipulate extended tie()d primitives. The solution to this problem is exposing tied() object interface as hybrid object interface and, thus, unifying interfaces of both tie()d and non-tied() primitives. This is something that can be achieved with specific design of hybrid class and by promote()ing primitives to become hybrids.
34            
35             As a result, promote()ing primitives with such hybrid class allows to write simple, non-specific code that can handle anything ranging from simple plain primitives to highly extended tied() objects.
36            
37             =end comment
38            
39             Thus, for class to qualify as hybrid class it must meet the following requirements (all of them met by just subclassing Object::Hybrid::CLASS):
40            
41             =over
42            
43             =item Equivalent perltie API
44            
45             For class to qualify as hybrid class it must implement perltie interface (all methods) for some type of primitive B that interface (perltie methods) must be equivalent to directly accessing underlying primitive - B tie()d and not tie()d. The equivalence requirement is what makes, say, next two lines to have exactly same effect for hash hybrids:
46            
47             $hybrid->{foo};
48             $hybrid->FETCH('foo'); # same
49            
50             There are several ways to comply with this requirement. If primitive is not tied(), then methods may operate on the primitive to produce same effect as corresponding direct access. There are two ways to modify the effect of direct access itself: tie() and dereference operators overloading. If primitive is tied(), then methods may delegate to tied() object to automatically achieve equivalence, or operate on tie()d primitive to produce same result as direct access would. If dereference operator is overloaded, corresponding access methods should take that into account and produce same result as overloaded direct access would.
51            
52             For example (this is approximately the way Object::Hybrid::CLASS does it):
53            
54             sub FETCH {
55             my $self = shift;
56            
57             return tied(%$self)->FETCH(@_)
58             if tied(%$self);
59            
60             return $self->{$_[0]}
61             }
62            
63             The equivalence requirement limits hybrid classes to include only a subset of tieclasses. For example, the Tie::StdHash (of standard Tie::Hash module) meets equivalence requirement for non-tied() primitives, while Tie::ExtraHash - not.
64            
65             In this context alternative to promote()ing primitive is tie()ing it to something like Tie::StdHash as it also will unify its interface with other tie()d hashes. However, this alternative gives up high speed of plain primitives (slowdown of about 30-40 times in some benchmarks), also tie()ing hash full of data may involve sizable costs of data copying, as well as this alternative cannot deliver same range of benefits hybrid objects can.
66            
67             For performnce comparison of various interface options see L section.
68            
69             =item Complete perltie API
70            
71             Currently tie()ing filehandles is still incomplete in Perl: sysopen(), truncate(), flock(), fcntl(), stat() and -X can't currently be trapped. However, hybrid class should implement equivalent samename methods and their uppercase aliases (SYSOPEN(), TRUNCATE(), FLOCK(), FCNTL(), STAT() and FTEST()) to fill the gap and also allow compatibility with tiehandle classes that also implement them. This will allow to write portable code that works around gaps in tiehandle implementation by B using methods on hybrids filehandles instead of Perl built-in functions, for example:
72            
73             promote $FH;
74            
75             $FH->stat();
76             $FH->ftest('-X');
77            
78             # same with indirect method calls...
79             STAT $FH;
80             FTEST $FH '-X';
81            
82             Thus, to avoid problems with gaps in tiehandle implementation simply always call methods on hybrids instead of Perl built-in functions.
83            
84             =item Delegation to tied() object
85            
86             In case of tied() primitives, hybrid class should delegate all methods to tied() object. Delegation invokes called method on tied() object instead of hybrid. This is required to allow invoking perltie methods B on hybrid directly (instead of on tied() object):
87            
88             $hybrid->STORE($value, @args);
89            
90             Although methods of the hybrid class may trigger methods of tied() class by simply accessing bless()ed primitive directly, they cannot pass arguments to them - this is why delegation is required.
91            
92             This requirement makes entire interface of tied() object exposed as hybrid object interface. Caller may use $hybrid->can('foo') to determine whether tied() object (if any) or hybrid implement certain methods.
93            
94             If tied() object provides no called method, delegation fails and then hybrid class is to fall back to calling its own samename method (of the hybrid class), called "fallback method", on hybrid itself. Fallback method should correspond to type of primitive, in case there are options (for example, FETCH() called for hash and array are supposed to be different methods). Fallback methods are simply same methods that would be called if hybrid is not tied(), meaning same methods of the non-tied() hybrid remain available if hybrid is tied(), except when tied() object exposes and replaces them with its own methods, if any, with same names.
95            
96             In particular, this is useful for defining methods that tied() class do not implement, e.g. to work around gaps in perltie implementation. For example, since stat() is not a perltie method currently, it is unlikely to be implemented by tiehandle class, but it is required to be defined for hybrid object to have portable C<< promote($tied_fh)->stat >> workarounds for currently broken C, so hybrid's stat() method may look like:
97            
98             sub stat { stat $_[0]->self }
99            
100             The stat() and few other methods provided by default hybrid class are similar to that, they use self() methods and because of that can work either for simple tied() classes like Tie::StdHash without any support form tied() class, or for tied() classes that just implement single additional self() method. If it is not possible for tied() class to define self() method, then tied() class may need to implement stat() and other methods itself.
101            
102             Note that for perltie methods fallback methods are almost never called, because normally tieclass does implement perltie methods. However, if it is not, the equivalence requirement is violated, since triggering perltie method implicitly, via primitive access, will lead to exception raised as call is on tied() object and corresponding perltie method is not provided, but calling that method on hybrid may end up calling fallback method, i.e. no exception is raised. Though, raising exception in both cases is not to be considered equivalence either.
103            
104             =item Method aliases
105            
106             Hybrid classes must provide altered-case aliases for all its methods (e.g. lowercased aliases for all perltie methods). The method alias name is returned by C<< Object::Hybrid->method_alias($method_name) >>.
107            
108             This requirement is especially relevant in case when there are same-name built-in functions for accessing primitives: shift(), exists(), seek(), etc. In this case and as general coding style for hybrids: the lower case should be used as functions or in direct method calls notation, while upper case can be used for indirect method call notation (later minimizes chances of indirect method notation colliding with non-parenthesized same name function calls). For example:
109            
110             seek $FH, 0, 0; # function call (coma after $FH, no extra arguments)
111             SEEK $FH 0, 0, @args; # indirect method call (no coma after $FH, @args extended interface)
112             $FH->seek(0, 0, @args); # direct method call (@args extended interface)
113            
114             Altered-case aliases are automatically provided by promote() to tied() interfaces of tie()d primitives - no need to modify tied() classes.
115            
116             In addition, calls of lower-case aliases must not fail (be fail-safe) due to method being not defined by either hybrid class or underlying tied() class (if any), becoming no-op and returning empty list or undef scalar. This allows to write portable code without (ab)using can() calls or eval{} wraps, which make code very combersome (in case of eval{} it is necessary to manually distinguish "can't find method" from errors in defined method). In contrast, upper-case aliases are not similarly fail-safe, calling them must be a fatal error if method, either lower-case or upper-case, is not defined, so that they can be used to ensure method is really called (mnemonics: insist on it being called by writing it in upper case). If, however, lower-case method is defined, the upper-case call will call it, not fail. For example:
117            
118             $hybrid->non_existing_method(); # must not fail due to "can't find method"
119             $hybrid->NON_EXISTING_METHOD(); # fatal "can't find method" error
120            
121             $hybrid->maybe_existing_method(); # must not fail due to "can't find method"
122             $hybrid->MAYBE_EXISTING_METHOD(); # may be a fatal "can't find method" error
123            
124             $filehandle_hybrid->fetch(); # must not fail due to "can't find method"
125             $filehandle_hybrid->FETCH(); # likely fatal "can't find method" error (since filehandles normaly have no FETCH()), but will call fetch() (not fail) if fetch() happens to be defined
126            
127             Implementing this feature of hybrid class requires autoloading non-defined methods and making AUTOLOAD to behave accordingly, but custom hybrid class can inherit it from default hybrid class.
128            
129             Automatic altered-case aliases provided by default hybrid class are relatively costly, as they involve extra can() call to locate altered-case method in case originally called method is not defined. However, this is only relevant for tie()d primitives as hybrid class (either default or custom) is likely to have both aliases defined and automatic aliasing is not used. To avoid cost of automatic aliasing in case of tie()d primitives, it is better to call method's aliase that is known (or likely) to be defined by underlying tied() class. This means that, for example, FETCH() is likely to be faster than fetch() for tie()d primitives (as their tied() classes usually define no fetch(), just FETCH()).
130            
131             =item fast() (aliase call())
132            
133             The fast() method must efficiently return tied() object for tie()d invocant and invocant itself for non-tied. The fast() method is used for performance optimization:
134            
135             $hybrid->fast->FETCH('a'); # for tied() $hybrid is much faster than...
136             $hybrid->FETCH('a');
137            
138             $hybrid->fast->can('foo'); # for tied() $hybrid is much faster than...
139             $hybrid->can('foo');
140            
141             For non-tied hybrids situation is reversed, but in absolute terms using fast() pays off, especially where tied hybrids are more common throughput. The tradeoff however is that $hybrid->fast->FETCH() provides no automatic aliasing, no fail-safety for non-defined methods, and do not fall back to methods of hybrid class in case tied() class defines no called method, so that using it is more risky and requires better knowledge of classes involved.
142            
143             =item self() method
144            
145             Any method of the hybrid object has only that object to operate on using either other hybrid methods or manipulating bless()ed primitive, either tie()d or not, directly. However, many tied() objects (like Tie::ExtraHash) transparently delegate operations on tie()d primitive to real primitive encapsulated somewhere inside that tied() object, using object just to store some additional state. If this is the case, tied() class may define self() as accessor for that underlying primitive to expose it to methods of the hybrid class and to users. As a result, methods of hybrid class would have access to both tie()d primitive and underlying real primitive. This can have a number of useful applications, in particular to work around gaps in tiehandle implementation and to increase performance, as it allows hybrid methods to bypass perltie layer and operate on underlying primitive directly, which may be bring large efficiency benefits for some bulk operations on underlying primitive. And in case of not tied() primitive self() simply returns that primitive.
146            
147             For example, since there is no yet perltie support for stat() and -X tests, called on tiehandle they do not propagate to underlying real filehandle, so they should be somehow propagated manually, but it requires knowing how to get underlying filehandle, if there is any, out of tied() object. Defining self() method in tieclass is supposed to do just that, and hybrid classes are expected to define self() method as well, so that portable code can simply be:
148            
149             promote $FH;
150            
151             stat $FH->self;
152             -X $FH->self;
153            
154             =for comment
155             # NOTE: the indirect notation will not work in this case, so beware...
156             #-X SELF $FH; # DON'T!
157             #-X SELF $FH; # DON'T!
158            
159             # or nearly same using methods (default implementations of these methods also use self() under the hood):
160             STAT $FH;
161             FTEST $FH '-X';
162             $FH->stat();
163             $FH->ftest('-X');
164            
165             Note that use of self() method (as in above code) is limited since it requires tiehandle class (with exception for simple tiehandle classes similar to Tie::StdHandle) to also implement self() method, and also there may be tiehandles that simply do not have (single) underlying filehandle or operate it non-transparently, but still it is useful for writing code portable across specific tiehandle classes that are known to support self(), so it is a requirement for hybrid classes (to be compatible).
166            
167             Simple tieclasses can define only self() method and automatically get sysopen(), truncate(), flock(), fcntl(), stat() and ftest() methods of default hybrid class to work for them correctly.
168            
169             The self() method is intended primarily for performance optimizations and to work around gaps in perltie (specifically, tiehandles) implementation. If there were no gaps, then there is no need for self() method except for performance optimization, as hybrid primitive is then B to (but much slower than) what self() is supposed to return. However, gaps lead to situations when hybrid primitive is not equivalent and you need to get underlying primitive that tied() object uses under the hood - self() method is expected to return just that. To work around gaps in a way compatible with hybrid objects, it is recommended that tieclasses either implement self(), if possible, or implement corresponding workaround methods themself (see L).
170            
171             Accordingly, as a requirement, hybrid class must provide self() method that simply returns the hybrid object:
172            
173             sub self { $_[0] }
174            
175             Since due to perltie implementation gaps hybrid object may be "non-equivalent" (as mentioned above) and, thus, of little use in case primitive is tie()d, self() may also return tied() object (in hope that tied() class is a simple Tie::StdHash-like class) in case self() of hybrid class is called as B (see L) for tied() hybrid, with return value at least checked to be of the same type as invocant and also that name of the tied() class has 'Std' in it:
176            
177             sub self {
178             my $self;
179             return $self
180             if $self
181             = Object::Hybrid->ref_tied($_[0])
182             and Object::Hybrid->ref_type($_[0])
183             eq Object::Hybrid->ref_type($self)
184             and ref($self) =~ /Std/;
185            
186             !$self or die("tied() class defines nor valid self() method for $_[0] (self() returned $self)");
187            
188             return $_[0]
189             }
190            
191             This is about how self() is implemented in default hybrid class. Although it works fine for simple tied() classes similar to Tie::StdHash (Tie::StdHandle, etc.) that have 'Std' in name (checked as an extra precaution), still for other, more complex such tied() classes this default self() may silently fail to deliver correct return value (there will be no exception raised by self() itself, but caller will hopefully soon notice things gone unexpected way), so tied() class may need to implement correct self() method itself. Finally, for many tieclasses it may not be possible to implement correct self() method, those tieclasses should implement self{} that simply raises an exception with proper explanations.
192            
193             The default hybrid class makes no use of self() except for working around tiehandle implementation gaps, so by default hybrids do not depend on tieclass implementing self() method.
194            
195             =item Optional bless() method
196            
197             If defined, C<< $hybrid_class->bless($primitive) >> method is called instead of otherwise calling bless($primitive, $hybrid_class). The bless() method is optional. It can be used as constructor/initializer for hybrid object, except it is to reuse $primitive (same as built-in bless()) instead of creating new. TIE*() and new() constructors are not used for that, as they are supposed to be perltie-style constructors and we keep them as such in case tieclass is subclassed to make hybrid class.
198            
199             =item C
200            
201             To mark complete valid hybrid class as such, put this lines into it:
202            
203             package CustomHybridClass;
204             use Object::Hybrid;
205             use Object::Hybrid::Class;
206            
207             This requirement allows to use the following test to find out if $class is a hybrid class:
208            
209             Object::Hybrid->Class->is($class);
210            
211             This test returns true for CustomHybridClass and all its subclasses, since subclasses of valid hybrid class are unlikely to (intentionally) make it invalid, but is not necessarily true in super-classes, as those may be not be (marked as) valid hybrid classes.
212            
213             Also:
214            
215             package CustomHybridClass;
216             use Object::Hybrid;
217             use Object::Hybrid::Class 'is mutable_class';
218            
219             is equivalent to mutable_class => 1 argument for promote(), i.e. allows CustomHybridClass to be always used as mutable hybrid class that allows hybrids being later tie()d or untie()d, changing interface accordingly (thoug mutable hybrid classes/objects are way slower).
220            
221             =back
222            
223             Provided that these requirements are met, hybrid class may be arbitrarily extended by adding custom parameters to perltie methods as well as adding custom, non-perltie methods.
224            
225             It is recommended that hybrid class subclasses Object::Hybrid::CLASS, as it at least ensures proper self() is defined. Or better subclass corresponding Object::Hybrid::FOO class (where FOO is either HASH, SCALAR, ARRAY or GLOB), so that only some methods may be overwritten. To automatically provide required altered-case aliasing of methods, use Object::Hybrid->methods() to define methods in subclass (see source of this module for examples).
226            
227             =head2 Overriding with methods()
228            
229             The methods() method should always be used to define methods in custom hybrid classes, as it automatically defines altered-case aliases for methods and can also define other aliases as well. For example, the following defines self(), self(), only(), ONLY(), directly(), DIRECTLY() as aliases for same method:
230            
231             Object::Hybrid->methods(
232             self => sub{ $_[0] },
233             only => 'self',
234             directly => 'self',
235             );
236            
237             =for comment Since indirect method notation unfortunately do not work for self(), the self() method name should read naturally in direct notation, and preferably be not readable in indirect notation to discourage its accidental use. This criteria reject just(), very() and leaves only(), directly(), itself() alternatives. The only() reads ok, but it reads ok in indirect notation too. The itself() reads nice with $FH, but not with, say, $LINES filehandle, etc. So, directly() seems to be viable alternative: stat $FH->directly.
238            
239            
240            
241             =head1 AUTHOR
242            
243             Alexandr Kononoff (L)
244            
245             =head1 COPYRIGHT AND LICENSE
246            
247             Copyright (c) 2010 Alexandr Kononoff (L). All rights reserved.
248            
249             This program is free software; you can use, redistribute and/or modify it either under the same terms as Perl itself or, at your discretion, under following Simplified (2-clause) BSD License terms:
250            
251             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
252            
253             * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
254             * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
255            
256             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
257            
258             =cut
259            
260            
261