File Coverage

blib/lib/UID.pm
Criterion Covered Total %
statement 52 53 98.1
branch 7 12 58.3
condition 0 4 0.0
subroutine 19 20 95.0
pod 0 1 0.0
total 78 90 86.6


line stmt bran cond sub pod time code
1             package UID;
2              
3              
4             ###############################################################################################################################
5              
6             =head1 NAME
7              
8             B E<8212> Create unique identifier constants
9              
10             =cut
11              
12             ###############################################################################################################################
13              
14            
15             =head1 VERSION
16              
17             Version 0.24 (April 16, 2009)
18              
19             =cut
20              
21             our $VERSION="0.24";
22              
23 1     1   24542 use strict; use warnings; use Carp; use utf8;
  1     1   2  
  1     1   37  
  1     1   5  
  1         3  
  1         25  
  1         4  
  1         3  
  1         76  
  1         5  
  1         2  
  1         4  
24            
25              
26             =head1 SYNOPSIS
27              
28             use UID "foo"; # define a unique ID
29             use UID BAR=>BAZ=>QUX=>; # define some more
30            
31             print foo==foo; # true
32             print foo==BAR; # false
33             print foo=="foo"; # false
34            
35             do_stuff(foo 42, BAR "bar", BAZ "foo");
36             # similar to do_stuff(foo=>42, BAR=>"bar", BAZ=>"foo")
37             # except the UID foo can be unambiguously distinguished from the string "foo"
38              
39              
40             =head1 DESCRIPTION
41              
42             The C module lets you declare unique identifiers E<8212> values that you can be sure will not be coincidentally matched by some other value.
43             The values are not "universally" unique (UUIDs/GUIDs); they are unique for a single run of your program.
44              
45             Define the identifiers with "C" followed by one or more strings for the names of the IDs to be created.
46             "C>" will create a unique constant called I that is a C object:
47             any value equal to I must be I itself (or a copy of it). No other UID (in the same process) will be equal to I.
48             Cs can be compared to each other (or to other values) using either C<==> or C (or C or C, of course).
49              
50             A typical use of C objects is to form a named pair (like C<< FOO=>42 >>), but note that the pair-comma (C<< => >>) implicitly
51             quotes the preceding word, so C<< FOO=>42 >> really means C<< "FOO"=>42 >>, using the string C<"FOO"> rather than the UID C.
52             However, a comma is not always needed; you can say simply C and often get the same effect as C.
53              
54              
55             =cut
56              
57            
58            
59             #===========================================================================
60             #
61             # UID
62             #
63             #===========================================================================
64              
65              
66             #UIDs are scoped to the package that imports them; could assign "globals" by tracking them in this package (some hash to store the names or something?) Is this really useful??? (other than for polluting namespaces)
67              
68             sub import
69             # Create a new UID of the given name
70             #
71             # We take a list of names and make subs out of them (like "use constant")
72             # The subs don't do anything; they just return a unique reference (object) --
73             # since the ref is anonymous, it will be unique (for this process)
74             # Needs to happen at compile time, so Perl will parse the sub/uid names nicely
75             # (our own "import" routine will export our identifiers at time)
76             {
77 4     4   3123 my $class=shift; # first arg will always be the package name
78            
79 4         19 for (@_) # each name requested
80             {
81 5 50 0     15 carp "WARNING: Ignoring UID '$_' because it is a ref/object, not a plain string" and next if ref $_; # UIDs can only be made out of plain strings (valid sub names) ###Should we allow this since the object will get stringified? or force the user to stringify it himself explicitly?
82 5 50 0     66 carp "WARNING: Ignoring UID '$_' because that name is already being used" and next if caller()->can($_); # hm, should be able to override this if using "no warnings redefine"!
83             #die rather than warn, unless the existing sub is also a UID??
84            
85 5         13 my $name=(caller()."::$_"); # fully qualified name
86 5         16 my $ID=bless [$_, $name], $class; # uniqueness: since the array-ref is lexically scoped here, we'll never get this exact ref any other way
87 1     1   182 no strict 'refs'; # because we're going to declare the sub using a name built out of a string
  1         2  
  1         153  
88 5 100   55   27 *{$name}=sub {return $ID, @_ if wantarray; croak "ERROR: attempt to use args after UID $ID which is in scalar context (perhaps you need a comma after $ID?)" if @_; return $ID};
  5 50       45  
  55         7410  
  48         105  
  48         203  
89             # if called in array context, return any args as well (allows us to use "UID x, y" without an extra comma);
90             # otherwise return just the UID ref itself; if we tried to pass args when being used in scalar context,
91             # complain, because those args would effectively be lost (list in scalar context uses only the first item)
92             }
93             }
94              
95             # We bless our refs so we can do some useful objecty stuff:
96 1     1   7675 use overload q(""), sub { "«$_[0][0]»" }; # stringifying will return the name so we can usefully print out our IDs in error messages, etc.
  1     12   1125  
  1         9  
  12         8279  
97 1     1   92 use overload '${}', sub {\ "«$_[0][1]»" }; # scalarly de-reffing also has the effect of stringifying, to return the fully qualified name
  1     2   2  
  1         5  
  2         21  
98              
99 19 100   19 0 876 sub compare { ref($_[0]) eq ref($_[1]) and overload::StrVal($_[0]) eq overload::StrVal($_[1]) }; # for comparing two UIDs (note that first we compare the class -- if they're different kinds of objects, they can't match; if they are, then we compare the actual "memory address" values of the underlying refs, which can only be the same if both sides are in fact the same UID
100 1     1   119 use overload "==", \&compare; use overload "!=", sub {not &compare};
  1     1   2  
  1     1   5  
  1         75  
  1         3  
  1         7  
  1         4  
101 1     1   69 use overload "eq", \&compare; use overload "ne", sub {not &compare};
  1     1   2  
  1     7   4  
  1         65  
  1         1  
  1         5  
  7         926  
102              
103 1 0   1   150 use overload nomethod=> sub { croak "ERROR: cannot use the '$_[3]' operator with a UID (in: ".($_[2]?"$_[1] $_[3] $_[0][0]":"$_[0][0]() $_[3] $_[1]").")" };
  1     0   2  
  1         7  
  0         0  
104              
105             ###TODO: add & or | for combining flags?
106             ###TODO: prolly should disallow redefining BEGIN, etc.! (plagiarise some more from use-constant)
107              
108              
109              
110             =head1 EXAMPLES
111              
112             Here is an example that uses UIDs for the names of named parameters.
113             Let's suppose we have a function (C) that takes for its arguments a list of items to do stuff to,
114             and an optional list of filenames to log its actions to.
115             Using ordinary strings to name the groups of arguments would look something like this:
116              
117             do_stuff(ITEMS=> $a, $b, $c, $d, $e, FILES=> $foo, $bar);
118              
119             The function can go through all the args looking for our "ITEMS" and "FILES" keywords.
120             However, if one of the items happened to be the string "FILES", the function would get confused.
121              
122             We could do something such as make the arguments take the form of a hash of array-refs
123             (a perfectly good solution, albeit one that requires more punctuation).
124             Or we could use UIDs (which actually allows for slightly less punctuation):
125              
126             use UID qw/ITEMS FILES/;
127            
128             do_stuff(ITEMS $a, $b, $c, $d, $e, FILES $foo, $bar);
129              
130             Now the function can check for the UID C unambiguously; no string or other object will match it.
131             Of course, you can still use I where it doesn't make sense (e.g., saying C;
132             but you can't make something else that is intended to be different but that accidentally turns out to be equal to I.
133              
134              
135             =head1 TECHNICALITIES
136              
137             Cs work by defining a subroutine of the given name in the caller's namespace.
138             The sub simply returns a UID object.
139             Any arguments that you feed to this sub are returned as well, which is why you can say C without a comma to separate the terms;
140             that expression simply returns the list C<(FOO, $bar)>.
141             (However, beware of imposing list context where it's not wanted: C puts C<$bar> in list context, as opposed to C.
142             Also, if you are passing UIDs as arguments to a function that has a prototype, a scalar prototype (C<$>)
143             can force the UID to return only itself, and a subsequent arg will need to be separated with a comma.)
144              
145             These subroutines work very much as do the constants you get from C>.
146             Of course, this means that the names chosen must be valid symbols (actually, you can call things almost anything in Perl,
147             if you're prepared to refer to them using circumlocutions like C<&{"a bizarre\nname"}>!).
148              
149             A UID overloads stringification to return a value consisting of its name when used as a string
150             (so C will display "C<«foo»>").
151             You can also treat it as a scalar-reference to get a string with the fully-qualified name
152             (that is, including the name of the package in which it lives: C).
153              
154             The comparison operators C<==> and C and their negations are also overloaded for UID objects:
155             comparing a UID to anything will return false unless both sides are UIDs;
156             and if both are, their blessed references are compared.
157             (Not the values the references are referring to, which are simply the UIDs' names, but rather the string-values of the refs,
158             which are based on their locations in memory E<8212>
159             since different references will always have different values, this guarantees uniqueness.)
160              
161              
162             =head1 ERROR MESSAGES
163              
164             =over 1
165              
166             =item WARNING: Ignoring UID '$_' because it is a ref/object, not a plain string
167              
168             You tried to make a UID out of something like an array-ref or an object.
169             The module is looking for a string or strings that it can define in your namespace, and will skip over this arg.
170              
171              
172             =item WARNING: Ignoring UID '$_' because that name is already being used
173              
174             A subroutine (or constant, or other UID, or anything else that really is also a sub)
175             has already been declared with the given name.
176             UID prevents you from redefining that name and skips over it.
177              
178              
179             =item ERROR: attempt to use args after UID $_ which is in scalar context (perhaps you need a comma after $_?)
180              
181             You put (what appear to be) arguments after a UID, but the UID is in scalar context, thus only a single value can be used
182             (not the UID plus its arguments). The solution is probably to put a comma after the UID, or strategically place some parentheses,
183             to separate it from the following item, rather than letting it take that item as an argument.
184              
185             =item ERROR: cannot use the '$_' operator with a UID
186              
187             You tried to operate on a UID with an operator that doesn't apply (which is pretty much all of them).
188             UIDs can be compared with C<==> or C, but you can't add, subtract, divide, xor them, etc.
189              
190             =back
191              
192              
193             =head1 BUGS & OTHER ANNOYANCES
194              
195             No particular bugs are known at the moment. Please report any problems or other feedback
196             to C<< >>, or through the web interface at L.
197              
198             Note that UIDs are less useful for hash keys, because the keys have to be strings, not objects.
199             You are able to use a UID as a key, but the stringified value (its name) will actually be used (and could conceivably
200             be accidentally duplicated).
201             However, there are modules that can give you hash-like behaviour while allowing objects as keys,
202             such as L or L or L.
203              
204             There are other places where Perl will want to interpret a UID (like any other sub name) as a string rather than as a function call.
205             Sometimes you need to say things like C<+FOO> or C to make sure C is evaluated as a UID and not as a string literal.
206             As mentioned, hash keys are one such situation; also C<< => >> implicitly quotes the preceding word.
207             Note that C<&FOO> will work to force the sub interpretation, but is actually shorthand for C<&FOO(@_)>,
208             i.e. it re-passes the caller's C<@_>, which is probably not what you want.
209              
210             Comparing a UID to something else (C) will correctly return true only if the C<$something> is
211             indeed (a copy of) the C object; but comparing something to a UID (C<$something==FOO>) could return an unexpected result.
212             This is because of the way Perl works with overloaded operators: the value on the left gets to decide the meaning of C<==> (or C).
213             Thus putting the UID first will check for UID-equality; if some other object comes first, it could manhandle the UID and compare,
214             say, its string value instead.
215             (It probably will work anyway, if the other code is well-behaved, but you should be aware of the possibility.)
216              
217             =cut
218              
219             ### Example of tricky object that deliberately confounds our UIDs:
220             # use UID foo;
221             # use overload q(==), sub {${$_[0]} eq ${$_[1]}}; use overload fallback=>1;
222             # my $x="«main::foo»"; my $o=bless \$x;
223             #
224             # print foo==$o?"Y":"N"; # uses UID's comparison, correctly says no
225             # print $o==&foo ?"Y":"N"; # uses cheater's comparison, says yes!
226              
227             =pod
228              
229             While C is slightly cleaner than C or C<< FOO=>$stuff >> [which would be an auto-quoted bareword anyway],
230             remember that C is actually implemented as a function call taking C<$a> and C<$b> as arguments;
231             thus it imposes list context on them. Most of the time this doesn't matter,
232             but if the item coming after a UID needs to be in scalar context, you may need to say something like C or C.
233              
234             The user should have more control over the warnings and errors that C spits out.
235              
236              
237              
238             =head1 COLOPHONICS
239              
240             Copyright 2007 David Green, C<< t cpan.org> >>
241              
242             Thanks to Tom Phoenix and others who contributed to C.
243              
244             This module is free software; you may redistribute it or modify it under the same terms as Perl itself. See L.
245              
246              
247             =cut
248              
249              
250             AYPWIP: "I think so, Brain, but then my name would be 'Thumby'!"