--- name: prototypes --- # Prototypes A prototype is a piece of metadata attached to a sub that controls **how the parser parses calls to that sub**. It does not control the runtime - it does not check argument counts at runtime, it does not enforce argument types, it does not affect method calls. Its job is to make a user-defined sub *parse* like a built-in. That narrow purpose is the one thing to remember about prototypes. Everything else follows. ## Declaration Two equivalent forms: ```perl sub mypush (\@@) { # short form my $aref = shift; push @$aref, @_; } sub mypush :prototype(\@@) { # attribute form ... } ``` The attribute form is mandatory when [signatures](signatures) are enabled in the same scope, because the parenthesised list after the sub name is then claimed by the signature. ## What prototypes actually do A prototype rewrites the parser's expectations at the call site: ```perl sub mygrep (&@) { my $code = shift; my @result; for (@_) { push @result, $_ if &$code } return @result; } mygrep { /foo/ } @items; # parses correctly thanks to (&@) # without the prototype, this is # a syntax error ``` The `(&@)` prototype tells the parser: "the first argument is a block (no `sub` keyword needed, no comma after it), the rest is a list." That's exactly how built-in `grep`, `map`, `sort` parse. This is the **only** place prototypes are useful: building DSL-shaped subs that look like built-ins. ## Prototype characters | Char | Meaning | |-------|--------------------------------------------------------------------| | `$` | scalar - forces scalar context on the argument | | `@` | list - eats all remaining arguments, list context | | `%` | hash - like `@` (even-length list) | | `&` | block / code ref - first position lets you omit `sub` | | `*` | bareword, scalar, typeglob, or globref | | `\$` | scalar reference - caller must pass `$var` form (auto-`\`-applied) | | `\@` | array reference - caller must pass `@arr` form | | `\%` | hash reference - caller must pass `%h` form | | `\&` | code reference - caller must pass `&fn` form | | `\[$@%&*]` | reference to any of the bracketed kinds | | `+` | scalar OR `\@`/`\%` (auto-detect array/hash literal) | | `;` | separates mandatory from optional | | `_` | scalar; if omitted, defaults to `$_` | | `()` | no arguments - and triggers the constant-fold optimisation | ## The `(\@)` vs `(@)` difference This is the whole reason backslash prototypes exist. ```perl sub takes_array (\@) { # backslash: pass-by-reference my ($aref) = @_; # @_ holds [array_ref] push @$aref, 'extra'; } sub takes_list (@) { # no backslash: pass-by-value (flattened) my @copy = @_; # @_ holds the flat list push @copy, 'extra'; # nothing in caller is affected } my @arr = (1, 2, 3); takes_array(@arr); # caller writes @arr - parser inserts \@arr # @arr is now (1, 2, 3, 'extra') takes_list(@arr); # caller writes @arr - flattens to a list # @arr is unchanged ``` The `(\@)` prototype lets you *write* the call as `takes_array(@arr)` - looking just like `push @arr, ...` - while the runtime actually receives an array reference. That's exactly what built-in `push`, `unshift`, `splice`, and `pop` look like to the user. ## The `()` constant-fold prototype A sub with the empty prototype `()` and a single-expression body that the optimiser can fold to a constant becomes inline-able: ```perl sub PI () { 3.14159265358979 } sub MAX () { 100 } my $area = PI * $r ** 2; # PI is inlined as 3.14159... ``` This is what `use constant` produces: ```perl use constant { PI => 3.14159265358979, MAX => 100, }; ``` Constant-folded subs are zero-cost at runtime. They do require the prototype to be `()` exactly (no arguments accepted), and the body must be a single expression that the optimiser can reduce. Adding `return` defeats the inlining: ```perl sub PI () { return 3.14159; } # NOT inlined: explicit return ``` ## What prototypes do *not* do - They do **not** check argument counts at runtime. Calling a `($)` sub with three arguments is a *parse-time* error, but if you bypass the parser (via `&fn(...)`, code refs, `goto &fn`), there is no runtime check. - They do **not** affect method calls. Method dispatch is resolved at runtime through `@ISA`, by which point parsing is long over. Prototypes on methods are silently ignored. - They do **not** affect calls through code references. The call site sees a scalar, not a name; no name, no prototype lookup. - They are **not** part of the sub's identity. `\&named_sub` loses prototype knowledge. ## When you should use prototypes You are writing a sub that looks like a built-in. Examples: - A list-eating function with a block argument (`(&@)`): `mygrep`, `myfirst`, `try { ... } catch { ... }`. - An "inplace" helper that mutates an array in place (`(\@)` or `(\@@)`). - A constant-like sub (`()` for inlining). That's the list. If you're not building a built-in look-alike, do not reach for prototypes - use a signature, or just unpack `@_`. ## When you should not use prototypes - For "type checking". Prototypes are not a type system. `sub f ($) { ... }` does not say "takes one scalar"; it says "takes one expression in scalar context". Calling `f(@arr)` passes the *count* of `@arr`, not the array itself, with no warning. - For run-time argument-count enforcement. Use signatures (which *do* enforce arity at runtime) or write the check by hand. - On methods. They are silently ignored. ## Forward declarations and recursion A recursive call sees the prototype only if the sub was predeclared: ```perl sub fact ($); # forward declaration with prototype sub fact ($) { my ($n) = @_; return $n <= 1 ? 1 : $n * fact($n - 1); # parsed with ($) in mind } ``` Without the forward declaration, the recursive `fact($n - 1)` inside the body is parsed before the `($)` prototype is finalised, so the prototype does not apply to that call. ## Inspecting a prototype at runtime [`prototype`](../perlfunc/prototype) returns a sub's prototype string (or `undef` if there is none): ```perl prototype(\&push); # '\@@' - yes, even built-ins have one prototype('CORE::push'); # '\@@' prototype(\&my_sub); # whatever you declared ``` ## See also - [`prototype`](../perlfunc/prototype) - runtime introspection. - [Signatures](signatures) - the modern alternative for arity and named-parameter handling. They coexist with prototypes but have different goals. - [`sub`](../perlfunc/sub) - the keyword's perlfunc page. - [Declaration](declaration) - where prototypes attach in the declaration syntax. - [Attributes](attributes) - `:prototype(...)` is the attribute form.