-
Notifications
You must be signed in to change notification settings - Fork 0
/
031-function-parameters.pl
executable file
·264 lines (212 loc) · 7.89 KB
/
031-function-parameters.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#!/usr/bin/env perl
# REF: http://modernperlbooks.com/books/modern_perl_2016/05-perl-functions.html#RnVuY3Rpb25QYXJhbWV0ZXJz
use 5.034;
use warnings;
use autodie;
use feature 'say';
use Test::More;
# A function receives its parameters in a single array, @_ (The Default Array Variables).
# When you invoke a function,
# Perl flattens all provided arguments into a single list.
# The function must either unpack its parameters into variables or operate on @_ directly:
sub greet_one {
my ($name) = @_;
say "Hello, $name!";
}
sub greet_all {
say "Hello, $_!" for @_;
}
# @_ behaves as a normal array.
# Most Perl functions shift off parameters or use list assignment,
# but you may also access individual elements by index:
sub greet_one_shift {
my $name = shift;
say "Hello, $name!";
}
sub greet_two_list_assignment {
my ( $hero, $sidekick ) = @_;
say "Well if it isn't $hero and $sidekick. Welcome!";
}
sub greet_one_indexed {
my $name = $_[0];
say "Hello, $name!";
# or, less clear
say "Hello, $_[0]!";
}
# You may also unshift, push, pop, splice, and use slices of @_.
# Remember that the array builtins use @_ as the default operand within functions,
# so that my $name = shift; works.
# Take advantage of this idiom.
# To access a single scalar parameter from @_,
# use shift,
# an index of @_,
# or lvalue list context parentheses.
# Otherwise, Perl will happily evaluate @_ in scalar context for you
# and assign the number of parameters passed:
sub bad_greet_one {
my $name = @_; # buggy
say "Hello, $name; you look numeric today!";
}
# List assignment of multiple parameters is often clearer than multiple lines of shift. Compare:
{
my $left_value = shift;
my $operation = shift;
my $right_value = shift;
}
# ... to:
{
my ( $left_value, $operation, $right_value ) = @_;
}
# The latter is simpler to read.
# As a side benefit, it has better runtime performance, though you're unlikely to notice.
# Occasionally you may see code which extracts parameters from @_
# and passes the rest to another function:
sub delegated_method {
my $self = shift;
say 'Calling delegated_method()'
# $self->delegate->delegated_method( @_ );
}
# Use shift when your function needs only a single parameter.
# Use list assignment when accessing multiple parameters.
# ================================
# Real Function Signatures
# ================================
# Perl 5.20 added built-in function signatures as an experimental feature.
# "Experimental" means that they may change or even go away in future releases of Perl,
# so you need to enable them to signal that you accept the possibility of rewriting code.
# NOTE: It is enabled automatically by a use v5.36 (or higher) declaration,
# or more directly by use feature 'signatures', in the current scope.
# REF: https://perldoc.perl.org/perlsub#Signatures
use experimental 'signatures';
# With that disclaimer in place, you can now write:
sub greet_one_using_signature($name) {
say "Hello, $name!";
}
# ... which is equivalent to writing:
sub greet_one_explicit {
die "Too many arguments for subroutine" if @_ < 1;
die "Too few arguments for subroutine" if @_ > 1;
my $name = shift;
say "Hello, $name!";
}
# You can make $name an optional variable by assigning it a default value:
sub greet_one_with_default( $name = 'Bruce' ) {
say "Hello, $name!";
}
# ... in which case writing greet_one( 'Bruce' ) and greet_one()
# will both ignore Batman's crime-fighting identity.
# You may use aggregate arguments at the end of a signature:
sub greet_all_using_signature( $leader, @everyone ) {
say "Hello, $leader!";
say "Hi also, $_." for @everyone;
}
sub make_nested_hash( $name, %pairs ) {
return { $name => \%pairs };
}
# ... or indicate that a function expects no arguments:
sub no_gifts_please() {
say 'I have too much stuff already.';
}
# ... which means that you'll get the Too many arguments for subroutine exception
# by calling that function with arguments.
# These experimental signatures have more features than discussed here.
# As you get beyond basic positional parameters,
# the possibility of incompatible changes in future versions of Perl increases, however.
# See perldoc perlsub's "Signatures" section for more details,
# especially in newer versions of Perl.
# Signatures aren't your only options.
# Several CPAN distributions extend Perl's parameter handling with additional syntax and options.
# Method::Signatures works as far back as Perl 5.8.
# Kavorka works with Perl 5.14 and newer.
# Despite the experimental nature of function signatures—
# or the additional dependencies of the CPAN modules—
# all of these options can make your code a little shorter and a little clearer both to read and to write.
# By all means experiment with these options to find out what works best for you and your team.
# Even sticking with simple positional parameters can improve your work.
# ================================
# Flattening
# ================================
# List flattening into @_ happens on the caller side of a function call.
# Passing a hash as an argument produces a list of key/value pairs:
{
my %pet_names_and_types = (
Lucky => 'dog',
Rodney => 'dog',
Tuxedo => 'cat',
Petunia => 'cat',
Rosie => 'dog',
);
show_pets(%pet_names_and_types);
sub show_pets {
my %pets = @_;
while ( my ( $name, $type ) = each %pets ) {
say "$name is a $type";
}
}
}
# When Perl flattens %pet_names_and_types into a list,
# the order of the key/value pairs from the hash will vary,
# but the list will always contain a key immediately followed by its value.
# Hash assignment inside show_pets() works the same way as the explicit assignment to %pet_names_and_types.
# This flattening is often useful,
# but beware of mixing scalars with flattened aggregates in parameter lists.
# To write a show_pets_of_type() function,
# where one parameter is the type of pet to display,
# pass that type as the first parameter
# (or use pop to remove it from the end of @_, if you like to confuse people):
{
sub show_pets_by_type {
my ( $type, %pets ) = @_;
while ( my ( $name, $species ) = each %pets ) {
next unless $species eq $type;
say "$name is a $species";
}
}
my %pet_names_and_types = (
Lucky => 'dog',
Rodney => 'dog',
Tuxedo => 'cat',
Petunia => 'cat',
Rosie => 'dog',
);
show_pets_by_type( 'dog', %pet_names_and_types );
show_pets_by_type( 'cat', %pet_names_and_types );
show_pets_by_type( 'moose', %pet_names_and_types );
}
# With experimental function signatures, you could write:
sub show_pets_by_type_using_signature( $type, %pets ) {
...;
}
# ================================
# Slurping
# ================================
# List assignment with an aggregate is always greedy,
# so assigning to %pets slurps all of the remaining values from @_.
# If the $type parameter came at the end of @_,
# Perl would warn about assigning an odd number of elements to the hash.
# You could work around that:
sub show_pets_by_type_demo_slurping {
my $type = pop;
my %pets = @_;
...;
}
# ... at the expense of clarity.
# The same principle applies when assigning to an array as a parameter.
# Use references (References) to avoid unwanted aggregate flattening.
# ================================
# Aliasing
# ================================
# @_ contains a subtlety; it aliases function arguments.
# In other words, if you access @_ directly,
# you can modify the arguments passed to the function:
{
sub modify_name {
$_[0] = reverse $_[0];
}
my $name = 'Orange';
modify_name($name);
say $name;
# prints egnarO
}
# Modify an element of @_ directly and you will modify the original argument.
# Be cautious and unpack @_ rigorously—or document the modification carefully.