File Coverage

blib/lib/MoobX.pm
Criterion Covered Total %
statement 73 76 96.0
branch 12 20 60.0
condition 1 2 50.0
subroutine 17 17 100.0
pod 3 6 50.0
total 106 121 87.6


line stmt bran cond sub pod time code
1             package MoobX;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Reactive programming framework heavily inspired by JavaScript's MobX
4             $MoobX::VERSION = '0.1.0';
5              
6 8     8   421965 use 5.20.0;
  8         23  
7              
8 8     8   2769 use MoobX::Observer;
  8         17  
  8         382  
9              
10             our @DEPENDENCIES;
11             our $WATCHING = 0;
12              
13 8     8   43 use Scalar::Util qw/ reftype refaddr /;
  8         7  
  8         424  
14 8     8   27 use Moose::Util qw/ with_traits /;
  8         12  
  8         54  
15 8     8   1566 use Module::Runtime 'use_module';
  8         10  
  8         41  
16 8     8   3883 use Graph::Directed;
  8         609478  
  8         236  
17              
18 8     8   59 use experimental 'signatures';
  8         10  
  8         59  
19              
20 8     8   1086 use parent 'Exporter::Tiny';
  8         10  
  8         58  
21              
22             our @EXPORT = qw/ observer observable autorun :attributes :traits /;
23              
24             our $WARN_NO_DEPS = 1;
25              
26             sub _exporter_expand_tag {
27 32     32   1255 my( $class, $name, $args, $globals ) = @_;
28              
29 32 100       108 if ( $name eq 'attributes' ) {
    50          
30 16         29 my $target = $globals->{into};
31              
32 8 50   8   38 eval qq{
  8     8   12  
  8         35  
  8         76  
  8         12  
  8         49  
  16         1194  
33             package $target;
34             use parent 'MoobX::Attributes';
35             1;
36             } or die $@;
37             }
38             elsif( $name eq 'traits' ) {
39 16         92 use_module( 'MoobX::Trait::'.$_) for qw/ Observer Observable /;
40             }
41              
42 32         471 return ();
43             }
44              
45             our $graph = Graph::Directed->new;
46              
47 108 50   108 0 227 sub observable_modified($obs) {
  108 50       179  
  108         104  
  108         141  
48              
49 108         443 my @preds = $graph->all_predecessors( refaddr $obs );
50              
51 108         11370 for my $pred ( @preds ) {
52 32         135 my $info = $graph->get_vertex_attribute(
53             $pred, 'info'
54             );
55              
56 32         1691 local @MoobX::DEPENDENCIES = ( @MoobX::DEPENDENCIES, $obs );
57 32         90 $info->clear_value;
58             }
59             }
60              
61 36 50   36 0 79 sub dependencies_for($self,@deps) {
  36         39  
  36         115  
  36         42  
62             $graph->delete_edges(
63             map {
64 36         209 refaddr $self => $_
  51         1330  
65             } $graph->successors(refaddr $self)
66             );
67              
68             $graph->add_edges(
69 36         6686 map { refaddr $self => refaddr $_ } @deps
  167         369  
70             );
71              
72             $graph->set_vertex_attribute(
73             refaddr $_, info => $_
74 36         14764 ) for $self, @deps;
75             }
76              
77             sub observable :prototype(\[$%@]) {
78 11     11 1 302 observable_ref( @_ );
79             }
80              
81             sub observable_ref {
82 31     31 0 61 my $ref = shift;
83              
84 31         100 my $type = reftype $ref;
85              
86 31   50     160 my $class = 'MoobX::'. ucfirst lc $type || 'SCALAR';
87              
88             $class = with_traits(
89 62         584 map { use_module($_) }
90 31         55 map { $_, $_ . '::Observable' } $class
  31         97  
91             );
92              
93 31 100       55195 if( $type eq 'SCALAR' ) {
    100          
    50          
    0          
94 9         16 my $value = $$ref;
95 9         56 tie $$ref, $class;
96 9         2922 $$ref = $value;
97             }
98             elsif( $type eq 'ARRAY' ) {
99 17         40 my @values = @$ref;
100 17         82 tie @$ref, $class;
101 17         7736 @$ref = @values;
102             }
103             elsif( $type eq 'HASH' ) {
104 5         18 my %values = %$ref;
105 5         35 tie %$ref, $class;
106 5         2495 %$ref = %values;
107             }
108             elsif( not $type ) {
109 0         0 my $value = $ref;
110 0         0 tie $ref, $class;
111 0         0 $ref = $value;
112             }
113              
114              
115 31         221 return $ref;
116              
117             }
118              
119 6     6 1 531 sub observer :prototype(&) { MoobX::Observer->new( generator => @_ ) }
120 4     4 1 73 sub autorun :prototype(&) { MoobX::Observer->new( autorun => 1, generator => @_ ) }
121              
122             1;
123              
124             __END__
125              
126             =pod
127              
128             =encoding UTF-8
129              
130             =head1 NAME
131              
132             MoobX - Reactive programming framework heavily inspired by JavaScript's MobX
133              
134             =head1 VERSION
135              
136             version 0.1.0
137              
138             =head1 SYNOPSIS
139              
140             use 5.20.0;
141              
142             use MoobX;
143              
144             my $first_name :Observable;
145             my $last_name :Observable;
146             my $title :Observable;
147              
148             my $address = observer {
149             join ' ', $title || $first_name, $last_name;
150             };
151              
152             say $address; # nothing
153              
154             $first_name = "Yanick";
155             $last_name = "Champoux";
156              
157             say $address; # Yanick Champoux
158              
159             $title = 'Dread Lord';
160              
161             say $address; # Dread Lord Champoux
162              
163             =head1 DESCRIPTION
164              
165             As I was learning how to use L<https://github.com/mobxjs/mobx|MobX>, I thought
166             it'd be fun to try to implement something similar in Perl. So I did.
167              
168             To set Moose object attributes to be observers or observables, take
169             a gander at L<MoobX::Trait::Observable> and L<MoobX::Trait::Observer>.
170              
171             To have an idea of the mechanics of MoobX, see the two blog entries in the SEE ALSO
172             section.
173              
174             This is also the early stages of life for this module. Consider everythign as alpha quality,
175             and the API still subject to huge changes.
176              
177             =head1 EXPORTED FUNCTIONS
178              
179             The module automatically exports 3 functions: C<observer>, C<observable> and C<autorun>.
180              
181             =head2 observable
182              
183             observable my $foo;
184             observable my @bar;
185             observable my %quux;
186              
187             Marks the variable as an observable, i.e. a variable which value can be
188             watched by observers, which will be updated when it changes.
189              
190             Under the hood, the variable is tied to the relevant L<MoobX::TYPE> class
191             L<MoobX::TYPE::Observable> role.
192              
193             If you want to declare the variable, assign it a value and set it as observable,
194             there are a few good ways to do it, and one bad:
195              
196             my $foo = 3;
197             observable $foo; # good
198              
199             observable( my $foo = 3 ); # good
200              
201             observable my $foo; # good
202             $foo = 3;
203              
204             observable my $foo = 3; # bad
205              
206             That last one doesn't work because Perl parses it as C<observable( my $foo ) = 3>,
207             and assigning values to non I<lvalue>ed functions don't work.
208              
209             Or, better, simply use the C<:Observable> attribute when you define the variable.
210              
211             my $foo :Observable = 2;
212             my @bar :Observable = 1..10;
213             my %baz :Observable = ( a => 1, b => 2 );
214              
215             =head2 observer
216              
217             observable my $quantity;
218             observable my $price;
219              
220             my $total = observer {
221             $quantity * $price
222             };
223              
224             $quantity = 2;
225             $price = 6.00;
226              
227             print $total; # 12
228              
229             Creates a L<MoobX::Observer> object. The value returned by the object will
230             react to change to any C<observable> values within its definition.
231              
232             Observers are lazy, meaning that they compute or recompute their values
233             when they are accessed. If you want
234             them to eagerly recompute their values, C<autorun> is what you want.
235              
236             If an observer function is run and doesn't report any dependency,
237             it'll emit the warning 'C<MoobX observer doesn't observe anything>',
238             because chances are there's something weird going on. The warning can
239             be silenced via the global variable C<$MoobX::WARN_NO_DEPS>.
240              
241             my $foo :Observable;
242              
243             my $debugging = 0;
244              
245             # if $debugging == 1, we'd get a warning
246             local $MoobX::WARN_NO_DEPS = 0;
247              
248             my $spy = observer {
249             return unless $debugging;
250              
251             say $foo;
252             };
253              
254             =head2 autorun
255              
256             observable my $foo;
257              
258             autorun {
259             say "\$foo is now $foo";
260             };
261              
262             $foo = 1; # prints '$foo is now 1'
263              
264             $foo = 2; # prints '$foo is now 2'
265              
266             Like C<observer>, but immediatly recompute its value when its observable dependencies change.
267              
268             =head1 SEE ALSO
269              
270             =over
271              
272             =item L<https://github.com/mobxjs/mobx|MobX> - the original inspiration
273              
274             =item L<http://techblog.babyl.ca/entry/moobx> and L<http://techblog.babyl.ca/entry/moobx-2> - the two blog entries that introduced MobX.
275              
276             =back
277              
278             =head1 AUTHOR
279              
280             Yanick Champoux <yanick@cpan.org>
281              
282             =head1 COPYRIGHT AND LICENSE
283              
284             This software is copyright (c) 2017 by Yanick Champoux.
285              
286             This is free software; you can redistribute it and/or modify it under
287             the same terms as the Perl 5 programming language system itself.
288              
289             =cut