/
Signature.pod6
558 lines (402 loc) · 18.7 KB
/
Signature.pod6
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
=begin pod
=TITLE class Signature
=SUBTITLE Parameter list pattern
class Signature {}
A signature is a static description of the L<parameter|Parameter> list
of a code object. That is, it describes what and how many arguments
you need to pass to the code or function in order to call it.
Passing arguments to a signature I<binds> the arguments, contained in
a L<Capture>, to the signature.
X<|signature literal (Signature)>
X<|:() (Signature)>
=head1 Signature Literals
Signatures appear inside parentheses after subroutine and method names, on
blocks after a C<< -> >> or C<< <-> >> arrow, as the input to
L<variable declarators|/language/variables#Variable_Declarators_and_Scope>
like L<C<my>|/syntax/my>, or as a separate term starting with a colon.
sub f($x) { }
# ^^^^ Signature of sub f
my method x() { }
# ^^ Signature of a method
my $s = sub (*@a) { }
# ^^^^^ Signature of an anonymous function
for <a b c> -> $x { }
# ^^ Signature of a Block
my ($a, @b) = 5, (6, 7, 8);
# ^^^^^^^^ Signature of a variable declarator
my $sig = :($a, $b);
# ^^^^^^^^ Standalone Signature object
Signature literals can be used to define the signature of a callback or a
closure.
sub f(&c:(Int)){}
sub will-work(Int){}
sub won't-work(Str){}
f(&will-work);
f(&won't-work);
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::AdHoc: Constraint type check failed for parameter '&c'»
f(-> Int { 'this works too' } );
Smart matching signatures against a List is supported.
my $sig = :(Int $i, Str $s);
say (10, 'answer') ~~ $sig;
# OUTPUT«True»
given ('answer', 10) {
when :(Str, Int) { say 'match' }
when $sig { say 'mismatch' }
}
# OUTPUT«match»
When smart matching against a Hash, the signature is assumed to consist of the keys of the Hash.
my %h = left => 1, right => 2;
say %h ~~ :(:$left, :$right);
# OUTPUT«True»
=head2 Parameter Separators
A signature consists of zero or more I<L<parameters|Parameter>>, separated by comma.
my $sig = :($a, @b, %c);
sub add($a, $b) { $a + $b };
As an exception the first parameter may be followed by a colon instead
of a comma to mark the invocant of a method. The invocant is the
object that was used to call the method, which is usually bound to L<C<self>>.
By specifying it in the signature, you can change the variable name it
is bound to.
method ($a: @b, %c){}; # first argument is the invocant
class Foo {
method whoami($me:) {
"Well I'm class $me.^name(), of course!"
}
}
say Foo.whoami; # => Well I'm class Foo, of course!
X<|type constraint (Signature)>
=head2 Type Constraints
X<|Constraint>
Parameters can optionally have a type constraint (the default is L<C<Any>>).
hese can be used to restrict the allowed input to a function.
=begin code :skip-test
my $sig = :(Int $a, Str $b);
sub divisors(Int $n) { $_ if $n %% $_ for 1..$n };
divisors 2.5;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT«X::TypeCheck::Argument: Calling divisors(Rat) will never work with declared signature (Int $n)»
=end code
X<|anonymouse arguments (Signature)>
Anonymous arguments are fine too, if a parameter is only needed for
its type constraint.
my $sig = :($, @, %a); # two anonymous and a "normal" parameter
$sig = :(Int, Positional); # just a type is also fine (two parameters)
sub baz(Str) { "Got passed a Str" }
Type constraints may also be L<type captures|/type/Signature#Type_Captures>.
X<|where clause (Signature)>
In addition to those I<nominal> types, additional constraints can
be placed on parameters in the form of code blocks which must return
a true value to pass the type check
sub f(Real $x where { $x > 0 }, Real $y where { $y >= $x }) { }
In fact it doesn't need to be a code block, anything on the right of the
C<where>-block will be used to smart-match the argument against it.
So you can also write
multi factorial(Int $ where 0) { 1 }
multi factorial(Int $x) { $x * factorial($x - 1) }
The first of those can be shortened to
multi factorial(0) { 1 }
i.e., you can use a literal directly as a type and value constraint
on an anonymous parameter.
=head3 Constraining Slurpy Arguments
L<Slurpy arguments|Slurpy_(A.K.A._Variadic)_Parameters> can not have type
constraints. A C<where>-clause in
conjunction with a L<Junction|/type/Junction> can be used to that effect.
sub f(*@a where {$_.all ~~ Int}){say @a};
f(42);
f(<a>);
# OUTPUT«[42]Constraint type check failed for parameter '@a' in sub f at ...»
=head3 X<Constraining Defined and Undefined Values|
type constraint,:D;type constraint,:U;type constraint,:_>
Normally, a type constraint only checks whether the value passed is of the
correct type.
sub limit-lines (Str $s, Int $limit) {
my @lines = $s.lines;
@lines[0 .. min @lines.elems, $limit].join("\n")
}
say (limit-lines "a \n b \n c \n d \n", 3).perl; # "a \n b \n c "
say limit-lines Str, 3;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Multi::NoMatch: Cannot resolve caller lines(Str: ); none of these signatures match:
# (Cool:D $: |c is raw)
# (Str:D $: :$count!, *%_)
# (Str:D $: $limit, *%_)
# (Str:D $: *%_)»
say limit-lines "a \n b", Int # Always returns the max number of lines
In this case, we really only want to deal with defined strings. To do this, we
use the C<:D> type constraint.
sub limit-lines (Str:D $s, Int $limit) {};
say limit-lines Str, 3;
CATCH { default { put .^name, .Str } };
# OUTPUT«X::AdHocParameter '$s' requires an instance of type Str, but a
# type object was passed. Did you forget a .new?»
This is much better than the way the program failed before, since here the
reason for failure is clearer.
It's also possible undefined types are the only ones that make sense for a
routine to accept. This can be constrained with the C<:U> type constraint. For
example, we can turn the C<&limit-lines> into a multi function to make use of
the C<:U> constraint.
=for code :allow<B L>
multi limit-lines (Str $s, Int:D $limit) {}
multi limit-lines (Str $s, Int:U $) { $s }
say limit-lines "a \n b \n c", Int; # "a \n b \n c"
For explicitly indicating the normal behaviour, C<:_> can be used, but this is
unnecessary. C<:(Num:_ $)> is the same as C<:(Num $)>.
=head3 X«Constraining signatures of Callables|function reference (constrain)»
To constrain block and subroutine references based on their signature write
the signature after the argument name.
sub f(&c:(Int, Str)) { say c(10, 'ten') };
sub g(Int $i, Str $s){ $s ~ $i };
f(&g);
# OUTPUT«ten10»
=head3 X«Constraining Return Types|-->;returns»
The token C<-->> followed by a type will force a type check on successful
execution of a routine. The return type arrow has to be placed at the end of
the parameter list. The keyword C<returns> following a signature declaration
has the same function. C<Nil> and its subclasses are ignored in return
constraints. This allowes L<Failure|/type/Failure> to be returned and passed on
down the call chain.
sub (--> Int) { my Int $i; $i };
sub (--> Int:D) { 1 }
sub () returns Int { 1 };
sub does-not-work(--> Int) { "" }; # throws X::TypeCheck::Return
If the type constraint is a constant expression, it is used as the return value
of the routine. Any return statement in that routine has to be argumentless.
sub foo(--> 123) { return }
L<C<Nil>|/type/Nil> and L<C<Failure>|/type/Failure> are always allowed as return types, regardless of any
type constraint.
sub foo(--> Int) { Nil };
say foo.perl; # Nil
Type captures and coercion types are not supported.
=head3 X<Coercion Type|coercion type (signature)>
To accept one type but coerce it automatically to another, use the accepted
type as an argument to the target type. If the accepted type is C<Any> it can
be omitted.
sub f(Int(Str) $want-int, Str() $want-str){ say $want-int.WHAT, $want-str.WHAT }
f '10', 10;
# OUTPUT«(Int)(Str)»
=head2 X<Slurpy (A.K.A. Variadic) Parameters|parameter,*@;parameter,*%,slurpy argument (Signature)>
An array or hash parameter can be marked as I<slurpy> by leading asterisk(s),
which means it can bind to an arbitrary amount of arguments (zero or more).
These are called "slurpy" because they slurp up any remaining arguments
to a function, like someone slurping up noodles.
$ = :($a, @b); # exactly two arguments, where the second one must be Positional
$ = :($a, *@b); # at least one argument, @b slurps up any beyond that
$ = :(*%h); # no positional arguments, but any number of named arguments
sub one-arg (@) { }
sub slurpy (*@) { }
one-arg (5, 6, 7); # ok, same as one-arg((5, 6, 7))
slurpy (5, 6, 7); # ok
slurpy 5, 6, 7 ; # ok
# one-arg(5, 6, 7) ; # X::TypeCheck::Argument
# one-arg 5, 6, 7 ; # X::TypeCheck::Argument
sub named-names (*%named-args) { %named-args.keys };
say named-names :foo(42) :bar<baz>; # foo bar
Note that positional parameters aren't allowed after slurpy
parameters.
=begin code :skip-test
:(*@args, $last);
CATCH { when X::Parameter::WrongOrder { put .^name, ': ', .Str } }
# OUTPUT«X::Parameter::WrongOrder: Cannot put required parameter $last after variadic parameters»
=end code
Slurpy parameters declared with one asterisk will flatten arguments by
dissolving one or more layers of bare C<Iterables>. Slurpy parameters declared
with two stars do not do so:
sub a(*@a) { @a.join("|").say };
a(1, [1, 2], ([3, 4], 5)); # 1|1|2|3|4|5
sub b(**@b) { @b.join("|").say };
b(1, [1, 2], ([3, 4], 5)); # 1|1 2|3 4 5
Normally a slurpy parameter will create an Array, create a new
Scalar container for each argument, and assign the value from each
argument to those Scalars. If the original argument also had an
intermediary Scalar it is bypassed during this process, and
is not available inside the called function.
Slurpy parameters have special behaviors when combined with some
L<traits and modifiers|#Parameter_Traits_and_Modifiers>,
as described below.
=head2 Single Argument Rule Slurpy
The single argument rule allows to treat arguments to subroutines,
C<for>-loops and list constructors based on context. Many methods on positional
types can work with a single arguments the same way as with a list or
arguments. Using C<+@> as a sigil in a Signature provides syntactic sugar to
make that task a little easier. Any single argument of a non-positional type
will be promoted to a list with a single item.
sub f(+@a){ dd @a };
f(1);
# OUTPUT«[1]»
f(1, 2, 3);
# OUTPUT«[1, 2, 3]»
my @b = <a b c>;
f @b;
# OUTPUT«["a", "b", "c"]»
=head2 Type Captures
X<|Type Capture (signature)>
Type Captures allow to defer the specification of a type constraint to the time
the function is called. They allow to refer to a type both in the signature and
the function body.
sub f(::T $p1, T $p2, ::C){
# $p1 and $p2 are of the same type T, that we don't know yet
# C will hold a type we derive from a type object or value
my C $closure = $p1 / $p2;
return sub (T $p1) {
$closure * $p1;
}
}
# The first parameter is Int and so must be the 2nd.
# We derive the 3rd type from calling the operator that is used in &f.
my &s = f(10, 2, Int.new / Int.new);
say s(2); # 10 / 2 * 2 == 10
X<|positional argument (Signature),named argument (Signature)>
=head2 Positional vs. Named
A parameter can be I<positional> or I<named>. All parameters are positional,
except slurpy hash parameters and parameters marked with a leading colon C<:>.
$ = :($a); # a positional parameter
$ = :(:$a); # a named parameter of name 'a'
$ = :(*@a); # a slurpy positional parameter
$ = :(*%h); # a slurpy named parameter
On the caller side, positional arguments are passed in the same order
as the parameters were declared.
sub pos($x, $y) { "x=$x y=$y" }
pos(4, 5); # x=4 y=5
In the case of named arguments and parameters, only the name is used for
mapping arguments to parameters
=for code :allow<L>
sub named(:$x, :$y) { "x=$x y=$y" }
named( y L«=>» 5, x => 4); # x=4 y=5
It is possible to have a different name for a named parameter than the
variable name:
sub named(:official($private)) { "Official business!" if $private }
named :official;
Aliases are also possible that way:
sub ( :color(:colour($c)) ) { } # 'color' and 'colour' are both OK
sub ( :color(:$colour) ) { } # same API for the caller
A function with named arguments can be called dynamically, dereferencing a
L<Pair|/type/Pair> with C<|> to turn it into a named argument.
multi f(:$named){ note &?ROUTINE.signature };
multi f(:$also-named){ note &?ROUTINE.signature };
for 'named', 'also-named' -> $n {
f(|($n => rand)) # «(:$named)(:$also-named)»
}
my $pair = :named(1);
f |$pair; # «(:$named)»
The same can be used to convert a Hash into named arguments.
my %pairs = also-named => 4;
sub f(|c) {};
f |%pairs; # «(:$also-named)»
X<|optional argument (Signature)>
=head2 Optional and Mandatory Parameters
Positional parameters are mandatory by default, and can be made optional
with a default value or a trailing question mark:
$ = :(Str $id); # required parameter
$ = :($base = 10); # optional parameter, default value 10
$ = :(Int $x?); # optional parameter, default is the Int type object
X<|mandatory named argument (Signature)>
Named parameters are optional by default, and can be made mandatory with a
trailing exclamation mark:
$ = :(:%config); # optional parameter
$ = :(:$debug = False); # optional parameter, defaults to False
$ = :(:$name!); # mandatory 'name' named parameter
Default values can depend on previous parameters, and are (at least
notionally) computed anew for each call
$ = :($goal, $accuracy = $goal / 100);
$ = :(:$excludes = ['.', '..']); # a new Array for every call
X<|destructuring arguments (Signature)>
=head2 Destructuring Parameters
Parameters can be followed by a sub-signature in brackets, which will
destructure the argument given. The destructuring of a list is just its
elements:
sub first(@array ($first, *@rest)) { $first }
or
sub first([$f, *@]) { $f }
While the destructuring of a hash is its pairs:
sub all-dimensions (% (:length(:$x), :width(:$y), :depth(:$z))) {
$x andthen $y andthen $z andthen True
}
In general, an object is destructured based on its attributes. A common idiom
is to unpack a L<C<Pair>>'s key and value in a for loop:
for <Peter Paul Merry>.pairs -> (:key($index), :value($guest)) {}
However, this unpacking of objects as their attributes is only the default
behavior. To make an object get destructured differently, change its
L<C<Capture>> method.
=head2 Subsignatures
To match against a compound parameter use a subsignature following the argument
name in parentheses.
sub foo(|c(Int, Str)){
put "called with {c.perl}"
};
foo(42, "answer");
# OUTPUT«called with \(42, "answer")»
X<|Long Names>
=head2 Long Names
To exclude certain parameters from being considered in multiple dispatch,
separate them with a double semi-colon.
multi sub f(Int $i, Str $s;; :$b) { dd $i, $s, $b };
f(10, 'answer');
# OUTPUT«10"answer"Any $b = Any»
=head2 X<Capture Parameters|parameter,|>
Prefixing a parameter with a vertical bar C<|> makes the parameter a
L<C<Capture>>, using up all the remaining positional and named
arguments.
This is often used in C<proto> definitions (like C<proto foo (|) {*}>) to
indicate that the routine's L<C<multi> definitions|multi> can have any L<type
constraints|#Type_Constraints>. See L<proto|/language/functions#proto> for an
example.
If bound to a variable arguments can be forwarded as a whole using the slip operator C<|>.
sub a(Int $i, Str $s) { say $i.WHAT, $s.WHAT }
sub b(|c) { say c.WHAT; a(|c) }
b(42, "answer");
# OUTPUT«(Capture)(Int)(Str)»
=head2 X<Parameter Traits and Modifiers|trait,is copy;trait,is rw>
By default, parameters are bound to their argument and marked as
read-only. One can change that with traits on the parameter.
The C<is copy> trait causes the argument to be copied, and allows it
to be modified inside the routine
sub count-up ($x is copy) {
$x = Inf if $x ~~ Whatever;
.say for 1..$x;
}
The C<is rw> trait makes the parameter only bind to a variable (or
other writable container). Assigning to the parameter changes the
value of the variable at the caller side.
sub swap($x is rw, $y is rw) {
($x, $y) = ($y, $x);
}
On slurpy parameters, C<is rw> is reserved for future use by language
designers.
The L<C<is raw> trait|/type/Parameter#method_raw> is automatically applied to
parameters declared with a L<backslash|/language/variables#Sigilless_variables>
as a "sigil", and may also be used to make normally sigiled parameters behave
like these do. In the special case of slurpies, which normally produce an
C<Array> full of C<Scalar>s as described above, C<is raw> will instead cause
the parameter to produce a C<List>. Each element of that list will be bound
directly as raw parameter.
=head1 Methods
=head2 method params
method params(Signature:D:) returns Positional
Returns the list of L<C<Parameter>> objects that make up the signature.
=head2 method arity
method arity(Signature:D:) returns Int:D
Returns the I<minimal> number of positional arguments required to satisfy
the signature.
=head2 method count
method count(Signature:D:) returns Real:D
Returns the I<maximal> number of positional arguments which can be bound
to the signature. Returns C<Inf> if there is a slurpy positional parameter.
=head2 method returns
Whatever the Signature's return constraint is:
:($a, $b --> Int).returns # Int
=head2 method ACCEPTS
multi method ACCEPTS(Signature:D: Capture $topic)
multi method ACCEPTS(Signature:D: @topic)
multi method ACCEPTS(Signature:D: %topic)
multi method ACCEPTS(Signature:D: Signature $topic)
The first three see if the argument could be bound to the capture,
i.e., if a function with that C<Signature> would be able to be called
with the C<$topic>:
(1, 2, :foo) ~~ :($a, $b, :foo($bar)); # True
<a b c d> ~~ :(Int $a); # False
The last returns C<True> if anything accepted by C<$topic> would also
be accepted by the C<Signature>.
:($a, $b) ~~ :($foo, $bar, $baz?); # True
:(Int $n) ~~ :(Str); # False
=end pod