File Coverage

blib/lib/Data/Monad/Base/Sugar.pm
Criterion Covered Total %
statement 77 77 100.0
branch 14 16 87.5
condition 3 6 50.0
subroutine 17 17 100.0
pod 5 5 100.0
total 116 121 95.8


line stmt bran cond sub pod time code
1             package Data::Monad::Base::Sugar;
2 14     14   68 use strict;
  14         25  
  14         414  
3 14     14   64 use warnings;
  14         24  
  14         434  
4 14     14   66 use Scalar::Util qw/blessed weaken/;
  14         22  
  14         2261  
5 14     14   76 use Exporter qw/import/;
  14         22  
  14         22413  
6              
7             our @EXPORT = qw/pick satisfy yield let/;
8              
9             our $_PICK = our $_SATISFY =
10             our $_YIELD = our $_LET = sub { die "called outside for()." };
11 32     32 1 140 sub pick($;$) { $_PICK->(@_) }
12 5     5 1 32 sub satisfy(&) { $_SATISFY->(@_) }
13 7     7 1 46 sub yield(&) { $_YIELD->(@_) }
14 5     5 1 27 sub let($$) { $_LET->(@_) }
15              
16             sub _capture {
17 181     181   198 my $ref = pop;
18              
19 181 100 66     684 return $ref->capture(@_) if blessed $ref && $ref->can('capture');
20              
21 151 100       460 ref $ref eq 'ARRAY' ? (@$ref = @_) : ($$ref = $_[0]);
22             }
23              
24 18     18   83 sub _tuple { bless [@_], 'Data::Monad::Base::Sugar::Tuple' }
25             sub Data::Monad::Base::Sugar::Tuple::capture {
26 30     30   35 my ($self, $result) = @_;
27 30 50 33     152 blessed $result && $result->isa(ref $self)
28             or die "[BUG]result is not tuple";
29              
30 30         97 _capture @{$result->[$_]} => $self->[$_] for 0 .. $#$self;
  60         111  
31             }
32              
33             sub for(&) {
34 12     12 1 4753 my $code = shift;
35              
36 12         20 my @blocks;
37             {
38 12         22 local $_PICK = sub {
39 32     32   50 my ($ref, $block) = @_;
40 32 100       90 $block = $ref, $ref = undef unless defined $block;
41              
42 32         219 push @blocks, {ref => $ref, block => $block};
43 12         89 };
44              
45             local $_YIELD = sub {
46 7     7   11 my $block = shift;
47              
48 7         75 $blocks[$#blocks]->{yield} = $block;
49 12         48 };
50              
51             local $_SATISFY = sub {
52 5     5   8 my $predicate = shift;
53 5 50       15 die "satisfy() should be called after pick()."
54             unless @blocks;
55              
56 5         8 my $slot = $#blocks;
57 5         8 my $orig_block = $blocks[$slot]->{block};
58 5         7 my $orig_ref = $blocks[$slot]->{ref};
59              
60             $blocks[$slot]->{block} = sub {
61             $orig_block->()->filter(sub {
62 18         29 _capture @_ => $orig_ref;
63 18         21 delete $blocks[$slot]; # destroy the cyclic ref
64 18         37 $predicate->(@_);
65 8         16 });
66 5         21 };
67 12         58 };
68              
69             local $_LET = sub {
70 5     5   6 my ($ref, $block) = @_;
71              
72 5 100       8 unless (@blocks) {
73             # eval immediately because we aren't in any lambdas.
74 1         3 _capture $block->() => $ref;
75 1         3 return;
76             }
77              
78 4         5 my $slot = $#blocks;
79 4         6 my $orig_block = $blocks[$slot]->{block};
80 4         5 my $orig_ref = $blocks[$slot]->{ref};
81              
82             # Capture multiple values.
83             # A tupple is used in "p <- e; p' = e'" pattern.
84             # See: http://www.scala-lang.org/docu/files/ScalaReference.pdf
85 4         8 $blocks[$slot]->{ref} = _tuple $orig_ref, $ref;
86             $blocks[$slot]->{block} = sub {
87             $orig_block->()->map(sub {
88 14         21 _capture @_ => $orig_ref;
89 14         18 delete $blocks[$slot]; # destroy the cyclic ref
90 14         36 return _tuple [@_], [$block->()];
91 6         10 });
92 4         16 };
93 12         54 };
94 12         255 $code->();
95             }
96              
97 12         28 my $weak_loop;
98             my $loop = sub {
99 85     85   155 my @blocks = @_;
100              
101 85         111 my $info = shift @blocks;
102 85         241 my $m = $info->{block}->();
103 85         196 my $ref = $info->{ref};
104              
105 85 100       240 if ($info->{yield}) {
    100          
106             return $m->map(sub {
107 15         25 _capture @_ => $ref;
108 15         45 $info->{yield}->();
109 11         59 });
110             } elsif (@blocks) {
111 38         46 my $retained_loop = $weak_loop;
112             return $m->flat_map(sub {
113 73         146 _capture @_ => $ref;
114 73         227 $retained_loop->(@blocks);
115 38         229 });
116             } else {
117 36         215 return $m;
118             }
119 12         57 };
120 12         134 weaken($weak_loop = $loop);
121              
122 12         39 return $loop->(@blocks);
123             }
124              
125             1;
126              
127             __END__