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 15     15   58 use strict;
  15         21  
  15         485  
3 15     15   68 use warnings;
  15         22  
  15         482  
4 15     15   64 use Scalar::Util qw/blessed weaken/;
  15         22  
  15         1724  
5 15     15   70 use Exporter qw/import/;
  15         18  
  15         12540  
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 103 sub pick($;$) { $_PICK->(@_) }
12 5     5 1 34 sub satisfy(&) { $_SATISFY->(@_) }
13 7     7 1 42 sub yield(&) { $_YIELD->(@_) }
14 5     5 1 21 sub let($$) { $_LET->(@_) }
15              
16             sub _capture {
17 181     181   140 my $ref = pop;
18              
19 181 100 66     462 return $ref->capture(@_) if blessed $ref && $ref->can('capture');
20              
21 151 100       306 ref $ref eq 'ARRAY' ? (@$ref = @_) : ($$ref = $_[0]);
22             }
23              
24 18     18   52 sub _tuple { bless [@_], 'Data::Monad::Base::Sugar::Tuple' }
25             sub Data::Monad::Base::Sugar::Tuple::capture {
26 30     30   24 my ($self, $result) = @_;
27 30 50 33     125 blessed $result && $result->isa(ref $self)
28             or die "[BUG]result is not tuple";
29              
30 30         41 _capture @{$result->[$_]} => $self->[$_] for 0 .. $#$self;
  60         79  
31             }
32              
33             sub for(&) {
34 12     12 1 2666 my $code = shift;
35              
36 12         15 my @blocks;
37             {
38 12         16 local $_PICK = sub {
39 32     32   35 my ($ref, $block) = @_;
40 32 100       78 $block = $ref, $ref = undef unless defined $block;
41              
42 32         155 push @blocks, {ref => $ref, block => $block};
43 12         43 };
44              
45             local $_YIELD = sub {
46 7     7   9 my $block = shift;
47              
48 7         67 $blocks[$#blocks]->{yield} = $block;
49 12         35 };
50              
51             local $_SATISFY = sub {
52 5     5   9 my $predicate = shift;
53 5 50       11 die "satisfy() should be called after pick()."
54             unless @blocks;
55              
56 5         7 my $slot = $#blocks;
57 5         9 my $orig_block = $blocks[$slot]->{block};
58 5         5 my $orig_ref = $blocks[$slot]->{ref};
59              
60             $blocks[$slot]->{block} = sub {
61             $orig_block->()->filter(sub {
62 18         25 _capture @_ => $orig_ref;
63 18         20 delete $blocks[$slot]; # destroy the cyclic ref
64 18         31 $predicate->(@_);
65 8         18 });
66 5         65 };
67 12         40 };
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         2 _capture $block->() => $ref;
75 1         3 return;
76             }
77              
78 4         4 my $slot = $#blocks;
79 4         4 my $orig_block = $blocks[$slot]->{block};
80 4         2 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         5 $blocks[$slot]->{ref} = _tuple $orig_ref, $ref;
86             $blocks[$slot]->{block} = sub {
87             $orig_block->()->map(sub {
88 14         16 _capture @_ => $orig_ref;
89 14         19 delete $blocks[$slot]; # destroy the cyclic ref
90 14         23 return _tuple [@_], [$block->()];
91 6         9 });
92 4         22 };
93 12         42 };
94 12         34 $code->();
95             }
96              
97 12         16 my $weak_loop;
98             my $loop = sub {
99 85     85   98 my @blocks = @_;
100              
101 85         70 my $info = shift @blocks;
102 85         161 my $m = $info->{block}->();
103 85         138 my $ref = $info->{ref};
104              
105 85 100       160 if ($info->{yield}) {
    100          
106             return $m->map(sub {
107 15         18 _capture @_ => $ref;
108 15         22 $info->{yield}->();
109 11         47 });
110             } elsif (@blocks) {
111 38         39 my $retained_loop = $weak_loop;
112             return $m->flat_map(sub {
113 73         93 _capture @_ => $ref;
114 73         115 $retained_loop->(@blocks);
115 38         139 });
116             } else {
117 36         127 return $m;
118             }
119 12         44 };
120 12         100 weaken($weak_loop = $loop);
121              
122 12         28 return $loop->(@blocks);
123             }
124              
125             1;
126              
127             __END__