-
Notifications
You must be signed in to change notification settings - Fork 0
/
035-closures.pl
executable file
·222 lines (182 loc) · 7.22 KB
/
035-closures.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env perl
# REF: http://modernperlbooks.com/books/modern_perl_2016/05-perl-functions.html#Q2xvc3VyZXM
use 5.034;
use warnings;
use autodie;
use feature 'say';
use Test::More;
# The computer science term higher order functions
# refers to functions which manipulate other functions.
# Every time control flow enters a function,
# that function gets a new environment representing that invocation's lexical scope (Scope).
# That applies equally well to anonymous functions (Anonymous Functions).
# The implication is powerful, and closures show off this power.
# ================================
# Creating Closures
# ================================
# A closure is a function that uses lexical variables from an outer scope.
# You've probably already created and used closures without realizing it:
# {
# use Modern::Perl '2015';
#
# my $filename = shift @ARGV;
#
# sub get_filename { return $filename }
# }
# If this code seems straightforward to you, good!
# Of course the get_filename() function can see the $filename lexical.
# That's how scope works!
{
# Suppose you want to iterate over a list of items
# without managing the iterator yourself.
# You can create a function which returns a function that,
# when invoked, will return the next item in the iteration:
sub make_iterator {
my @items = @_;
my $count = 0;
return sub {
return if $count == @items;
return $items[ $count++ ];
}
}
my $cousins = make_iterator(
qw(
Rick Alex Kaycee Eric Corey Mandy Christine Alex
)
);
say $cousins->() for 1 .. 6;
# Even though make_iterator() has returned,
# the anonymous function stored in $cousins has closed over the values of these variables
# as they existed within the invocation of make_iterator()—
# and their values persist (Reference Counts).
# Because invoking make_iterator() creates a separate lexical environment,
# the anonymous sub it creates and returns closes over a unique lexical environment for each invocation:
my $aunts = make_iterator(
qw(
Carole Phyllis Wendy Sylvia Monica Lupe
)
);
say $cousins->();
say $aunts->();
}
# Because make_iterator() does not return these lexicals by value or by reference,
# only the closure can access them.
# They're encapsulated as effectively as any other lexical is,
# although any code which shares a lexical environment can access these values.
# This idiom provides better encapsulation of what would otherwise be a file or package global variable:
{
# within this lexical scope
my $private_variable;
sub set_private { $private_variable = shift }
sub get_private { $private_variable }
}
# Named functions have package global scope,
# thus you cannot nest named functions.
# Any lexical variables shared between nested functions will go unshared
# when the outer function destroys its first lexical environment.
# Perl will warn you when this happens.
# The CPAN module (PadWalker) lets you violate lexical encapsulation,
# but anyone who uses it gets to fix any bugs that result.
# ================================
# Uses of Closures
# ================================
# Iterating over a fixed-sized list with a closure is interesting,
# but closures can do much more,
# such as iterating over a list which is too expensive to calculate
# or too large to maintain in memory all at once.
# Consider a function to create the Fibonacci series as you need its elements
# (probably so you can check the output of your Haskell homework).
# Instead of recalculating the series recursively,
# use a cache and lazily create the elements you need:
{
sub gen_fib {
my @fibs = ( 0, 1 );
return sub {
my $item = shift;
if ( $item >= @fibs ) {
for my $calc ( @fibs .. $item ) {
$fibs[$calc] = $fibs[ $calc - 2 ] + $fibs[ $calc - 1 ];
}
}
return $fibs[$item];
}
}
# calculate 42nd Fibonacci number
my $fib = gen_fib();
say $fib->(42);
}
# Every call to the function returned by gen_fib() takes one argument,
# the nth element of the Fibonacci series.
# The function generates and caches all preceding values in the series as necessary,
# and returns the requested element.
# Here's where closures and first class functions get interesting.
# This code does two things;
# there's a pattern specific to caching intertwined with the numeric series.
# What happens if you extract the cache-specific code
# (initialize a cache, execute custom code to populate cache elements, and return the calculated or cached value)
# to a function gen_caching_closure()?
# TODO: review
sub gen_caching_closure {
my ( $calc_element, @cache ) = @_;
return sub {
my $item = shift;
$calc_element->( $item, \@cache ) unless $item < @cache;
return $cache[$item];
};
}
sub gen_fib_extracted {
my @fibs = ( 0, 1, 1 );
return gen_caching_closure(
sub {
my ( $item, $fibs ) = @_;
for my $calc ( ( @$fibs - 1 ) .. $item ) {
$fibs->[$calc] = $fibs->[ $calc - 2 ] + $fibs->[ $calc - 1 ];
}
},
@fibs
);
}
# The program behaves as it did before,
# but now function references and closures separate the cache initialization behavior
# from the calculation of the next number in the Fibonacci series.
# Customizing the behavior of code—
# in this case, gen_caching_closure()—
# by passing in a function allows tremendous flexibility and can clean up your code.
# The builtins map, grep, and sort are themselves higher-order functions.
# ================================
# Closures and Partial Application
# ================================
# Closures can also remove unwanted genericity.
# Consider the case of a function which takes several parameters:
sub make_sundae {
my %args = @_;
my $ice_cream = get_ice_cream( $args{ice_cream} );
my $banana = get_banana( $args{banana} );
my $syrup = get_syrup( $args{syrup} );
...;
}
# Myriad customization possibilities might work very well in a full-sized ice cream store,
# but for an ice cream cart where you only serve French vanilla ice cream on Cavendish bananas,
# every call to make_sundae() passes arguments that never change.
# Partial application allows you to bind some of the arguments to a function now
# so that you can provide the others later.
# Wrap the function you intend to call in a closure and pass the bound arguments.
# For your ice cream cart:
{
my $make_cart_sundae = sub {
return make_sundae(
@_,
ice_cream => 'French Vanilla',
banana => 'Cavendish',
);
};
}
# Now whenever you process an order,
# invoke the function reference in $make_cart_sundae
# and pass only the interesting arguments.
# You'll never forget the invariants or pass them incorrectly.
# You can even use Sub::Install from the CPAN to import $make_cart_sundae function into another namespace.
# This is only the start of what you can do with higher order functions.
# Mark Jason Dominus's Higher Order Perl is the canonical reference on first-class functions and closures in Perl.
# Read it online at http://hop.perl.plover.com/.
done_testing();