File Coverage

blib/lib/Foreign/Sort.pm
Criterion Covered Total %
statement 39 39 100.0
branch n/a
condition n/a
subroutine 10 10 100.0
pod 0 1 0.0
total 49 50 98.0


line stmt bran cond sub pod time code
1             package Foreign::Sort;
2 2     2   67481 use strict;
  2         13  
  2         56  
3 2     2   9 use warnings;
  2         4  
  2         48  
4 2     2   1053 use Attribute::Handlers;
  2         7898  
  2         10  
5             our $VERSION = '0.01';
6              
7             sub import {
8 3     3   26 my ($pkg, @others) = shift;
9 3         6 my $caller = caller;
10 2     2   130 no strict 'refs';
  2         5  
  2         189  
11 3         8 for my $p ($caller, @others) {
12 3         5 push @{"$p\::ISA"}, $pkg;
  3         164  
13             }
14             }
15              
16             sub Foreign : ATTR(CODE) {
17 2     2 0 1773 my ($pkg, $sym, $code) = @_;
18 2         3 my $p2 = *{$sym}{PACKAGE};
  2         4  
19 2     2   15 no warnings 'redefine';
  2         7  
  2         98  
20 2         6 *{ $sym } = sub {
21 2     2   12 no strict 'refs';
  2         4  
  2         234  
22 20     20   3236 my $p1 = caller;
23 20         35 local ${"$p2\::a"} = ${"$p1\::a"};
  20         46  
  20         44  
24 20         34 local ${"$p2\::b"} = ${"$p1\::b"};
  20         38  
  20         35  
25 20         41 $code->();
26 2         10 };
27 2     2   14 }
  2         4  
  2         16  
28              
29             1;
30              
31             =head1 NAME
32              
33             Foreign::Sort - subroutine attribute to allow call to sort routine from other package
34              
35             =head1 VERSION
36              
37             0.01
38              
39             =head1 SYNOPSIS
40              
41             package X1;
42             use Foreign::Sort;
43             sub by_middle : Foreign {
44             (substr($a,4) // "") cmp (substr($b,4) // "")
45             || $a cmp $b
46             }
47              
48             package X2;
49             @env_keys = sort X1::by_middle keys %ENV;
50              
51             =head1 THE PROBLEM
52              
53             The builtin L<"sort"|perlfunc/"sort"> function takes an optional
54             subroutine name to use as a comparison function. Just before calling
55             the comparison function, Perl temporarily sets the variables
56             C<$a> and C<$b> from the calling package with the values to be compared.
57             The comparison function is expected to decide an ordering for
58             C<$a> and C<$b> and to return an appropriate value.
59              
60             A problem arises when the calling package is not the same as the
61             package that defines the comparison function.
62              
63             package X2;
64             sort by_42 {
65             ($b eq '42') <=> ($a eq '42) || $a <=> $b
66             }
67              
68             @y = (17, 19, 42, 83, 47);
69             @yy = sort X2::by_42 @y;
70              
71             package X1;
72             @x = (17, 19, 42, 83, 47);
73             @xx = sort X2::by_42 @x;
74              
75             The first C call will succeed (returning the values in the
76             order C<42,17,18,47,83>) but the second C call will fail.
77             This is because the C function, declared in package C,
78             is implictly operating on the package variables C<$X2::a> and
79             C<$X2::b>, and the sort call from package C is setting the
80             package variables C<$X1::a> and C<$X1::b> instead.
81              
82             One relatively common place this problem arises is in inheritance
83             heirarchies, where it may be cumbersome to use a comparison function
84             in a superclass from a subclass.
85              
86             =head1 THE SOLUTION
87              
88             The C package defines the subroutine attribute
89             C that can be applied to comparison functions.
90             A comparison function with the C attribution will
91             perform its comparison on the C<$a> and C<$b> values from
92             the I package, not (necessarily) the package where
93             the comparison function is defined. This allows you to define
94             a comparison function that other users may call from other
95             packages, and save them the trouble of setting
96             C<$a> and C<$b> in the right package.
97              
98             package X2;
99             use Foreign::Sort;
100             sub by_42 : Foreign {
101             ($b eq '42') <=> ($a eq '42) || $a <=> $b
102             }
103              
104             package X1;
105             @x = (17, 19, 42, 83, 47);
106             @xx = sort X2::by_42 @x;
107              
108             In this case, the call succeeds because the C
109             package was copying the values from C<$X1::a> and C<$X1::b>
110             to C<$X2::a> and C<$X2::b> with each call to the C
111             function.
112              
113             This module was inspired by a discussion at
114             L.
115              
116             =head1 LIMITATIONS
117              
118             All testing for initial release done on Perl v5.22 and better.
119             Future versions will attempt to make this module compatible
120             with older Perls, if necessary.
121              
122             =head1 SUPPORT
123              
124             You can find documentation for this module with the perldoc command.
125              
126             perldoc Foreign::Sort
127              
128              
129             You can also look for information at:
130              
131             =over 4
132              
133             =item * RT: CPAN's request tracker (report bugs here)
134              
135             L
136              
137             =item * AnnoCPAN: Annotated CPAN documentation
138              
139             L
140              
141             =item * CPAN Ratings
142              
143             L
144              
145             =item * Search CPAN
146              
147             L
148              
149             =back
150              
151              
152             =head1 LICENSE AND COPYRIGHT
153              
154             Copyright (c) 2019, Marty O'Brien.
155              
156             This library is free software; you can redistribute it and/or modify
157             it under the same terms as Perl itself, either Perl version 5.10.1 or,
158             at your option, any later version of Perl 5 you may have available.
159              
160             See http://dev.perl.org/licenses/ for more information.
161              
162             =cut