File Coverage

blib/lib/Error/ROP.pm
Criterion Covered Total %
statement 24 24 100.0
branch 4 4 100.0
condition n/a
subroutine 9 9 100.0
pod 0 4 0.0
total 37 41 90.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Error-ROP - A simple and lightweight implementation error handling library for Perl,
2             # inspired in the Either type.
3             package Error::ROP;
4 5     5   557647 use strict;
  5         13  
  5         140  
5 5     5   24 use warnings;
  5         8  
  5         113  
6 5     5   1251 use Error::ROP::Imp;
  5         61  
  5         405  
7 5     5   64 use Exporter qw/import/;
  5         12  
  5         1557  
8              
9             our @EXPORT_OK = qw/success failure rop bind/;
10             our $VERSION = '0.04';
11              
12             sub success {
13 19     19 0 3805 return Error::ROP::Imp->new(value => shift);
14             }
15              
16             sub failure {
17 9     9 0 1355 return Error::ROP::Imp->new(failure => shift);
18             }
19              
20             sub rop (&) {
21 20     20 0 29914 my $code = shift;
22              
23 20         37 my $res = eval {
24 20         49 $code->(@_);
25             };
26 20 100       217 return failure($@) if $@;
27 14         38 return success($res);
28             }
29              
30             # Either a -> (a -> Either b) -> Either b
31             sub bind {
32 2     2 0 10 my $either = shift @_;
33 2         3 my $fn = \&{shift @_};
  2         3  
34 2 100   1   9 return $either->is_valid ? rop { $fn->($either->value) } : $either;
  1         23  
35             }
36              
37             1;
38              
39             =encoding utf8
40              
41             =head1 NAME
42              
43             Error::ROP - A simple and lightweight implementation error handling library for Perl,
44             inspired in the Rop type.
45              
46             =head1 SYNOPSIS
47              
48             use Error::ROP qw(rop);
49              
50             my $meaning = rop { 80 / $divisor }->then(sub { $_ + 2 });
51              
52             say "The life meaning is " . $meaning->value if $meaning->is_valid;
53             warn "Life has no meaning" if not $meaning->is_valid;
54              
55             =head1 DESCRIPTION
56              
57             The purpose of the C<< rop >> function is to let you focus in the happy path
58             and provide a nice way to treat failures without filling the code
59             with C<< eval >>s and C<< if >>s that always serve almost the same purpose.
60              
61             Supose you have a computation that can fail depending on some condition.
62             For the sake of simplicity consider the following code
63              
64             sub compute_meaning {
65             my $divisor = shift;
66             return 2 + 80 / $divisor;
67             };
68              
69             that will fail when called with a zero argument.
70              
71             Following the style of the L<Railway Oriented Programming|https://fsharpforfunandprofit.com/rop/>, you wrap the part
72             that could fail in a C<< rop >> block and focus on programming the happy
73             path:
74              
75             sub compute_meaning {
76             my $divisor = shift;
77             return rop { 80 / $divisor }
78             ->then(sub { $_ + 2 });
79             };
80              
81             This way, the C<< compute_meaning >> function will never blow, even when
82             passed in a zero argument and the computation doesn't make sense. The caller
83             can check that the computation succeeded by asking the C<< rop >> result
84             object.
85              
86             When the computation succeeds, the C<< value >> property contains
87             the computation result
88              
89             my $meaning = compute_meaning(2);
90             say "The life meaning is " $meaning->value if $meaning->is_valid;
91              
92             and when the computation fails, you can also inform the user or decide how to
93             proceed, by inspecting the C<< failure >> value, which will contain the captured
94             error.
95              
96             my $meaning = compute_meaning(0);
97             warn "Life has no meaning: " . $meaning->failure if not $meaning->is_valid;
98              
99              
100             =head2 Chaining
101              
102             The real usability gain of using C<< rop >> occurs when you have a recipe
103             that comprises several things to do and you need to stop at the first step
104             that fails.
105              
106             That is, you need to chain or compose several functions that
107             in the happy path would be executed one after another but in the real path, you
108             would have to check for any of them if had failed or not and proceed with
109             the next or stop and report the errors.
110              
111             With C<< rop >> you can leverage the checking to the library and just program
112             the happy path functions and chain them with the C<< then >> method:
113              
114             use Error::ROP;
115              
116             my $res = rop { 40 / $something }
117             ->then(sub { $_ / 2 })
118             ->then(sub { $_ * 4 })
119             ->then(sub { $_ + 2 });
120              
121             You can always know if the computation has succed by inspecting the rop,
122              
123             say $res->value if $res->is_valid;
124              
125             or treat the error otherwise
126              
127             warn $res->failure if not $res->is_valid;
128              
129             The computation will short-circuit and return with the first error occurred,
130             no matter how many chained functions remain after the failing step.
131              
132             =head2 On Either types and then
133              
134             This module does not implement the Either type in Perl. The Haskell, F#, ML and
135             other strongly typed functional programming languages have Either types. This
136             is not a generic type like Haskell's C<Either a b>.
137              
138             On those PL you have a strong type system and generic programming facilities that
139             allow you to generalize operations into higher abstractions. In particular, you
140             can operate in elevated (monadic) types as if they where first class values and the
141             languages provide tools (generic functions and operators) that allow you to
142             compose those operations by somehow overloading composition.
143              
144             When adopting an Either type to implement ROP in those languages, you normally use
145             the C< E<gt>=E<gt> > operator to overload composition. Actually, you use it to compose
146             functions of the type
147              
148             >=> :: (a -> Either b e) -> (b -> Either c e) -> (a -> Either c e)
149              
150             This library just uses a wrapper object (the Error::ROP instance) that has a method C<then> to somehow
151             compose other operations. This is a much less flexible approach but it works and is easy to understand.
152             The two leaves of the type are accessible via the instance's C<value> and C<failure> getters.
153              
154             The only confusion might be that it ressembles the C<then> function of a promise or future. This is not
155             exactly the same. Just keep that in mind.
156              
157             =head2 USAGE
158              
159             You can find more usage examples in the tests C<t/Then.t>. For examples of
160             how to use inside Moose C<t/Example.t>
161              
162             =head2 Running tests
163              
164             A C<Dockerfile> is provided in order to run the tests without needing
165             any perl in your system. Just run:
166              
167             $ make -f Makefile.docker test
168              
169             This should construct an image with the necessary dependencies, copy
170             the source into the image and run the tests.
171              
172             =head1 AUTHOR
173              
174             L<Pau Cervera i Badia|pau.cervera@capside.com>
175              
176             CAPSiDE
177              
178             =head1 BUGS and SOURCE
179              
180             The source code is located here: L<https://github.com/paudirac/Error-ROP>
181              
182             Please report bugs to: L<https://github.com/paudirac/Error-ROP/issues>
183              
184             =head1 COPYRIGHT and LICENSE
185              
186             Copyright (c) 2017 by CAPSiDE
187              
188             This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module.
189              
190             =cut