Subroutines and Signatures
Raku's subroutine system is where the language really starts to shine. While you can write simple functions just like in any other language, Raku's signatures give you type checking, multiple dispatch, and pattern matching right in the function declaration. Let us start with the basics and build up.Declaring a Subroutine
The simplest subroutine takes no parameters and returns nothing special:Both calling styles work. Parentheses are optional when there is no ambiguity.sub greet { say "Hello, World!"; } greet; # Hello, World! greet(); # also works with parens
Positional Parameters
Positional parameters are listed in the signature after the sub name:Multiple positional parameters:sub greet($name) { say "Hello, $name!"; } greet("Alice"); # Hello, Alice!
Thesub add($a, $b) { return $a + $b; } say add(3, 7); # 10
return keyword is optional. The last expression in the sub is the return value:
sub add($a, $b) { $a + $b; } say add(3, 7); # 10
Named Parameters
Named parameters start with a colon in the signature and are passed with the same colon syntax:Named parameters can be passed in any order:sub create-user(:$name, :$email) { say "Creating user: $name ($email)"; } create-user(name => "Alice", email => "alice@example.com"); # or using colon-pair syntax: create-user(:name<Alice>, :email<alice@example.com>);
create-user(email => "bob@test.com", name => "Bob"); # Creating user: Bob (bob@test.com)
Mixing Positional and Named
sub send-message($text, :$to, :$urgent = ) { my $prefix = $urgent ?? "[URGENT] " !! ""; say "$prefix$text -> $to"; } send-message("Hello", to => "Alice"); # Hello -> Alice send-message("Server down!", to => "Admin", urgent => ); # [URGENT] Server down! -> Admin
Optional Parameters
Positional parameters are required by default. To make one optional, add a?:
Named parameters are optional by default. To make one required, addsub greet($name, $greeting?) { my $g = $greeting // "Hello"; say "$g, $name!"; } greet("Alice"); # Hello, Alice! greet("Alice", "Bonjour"); # Bonjour, Alice!
!:
sub login(:$username!, :$password!) { say "Logging in $username..."; } login(username => "admin", password => "secret"); # login(username => "admin"); # ERROR: required named parameter 'password' not passed
Default Values
Both positional and named parameters can have defaults:sub connect($host = "localhost", :$port = 8080, :$ssl = ) { my $protocol = $ssl ?? "https" !! "http"; say "Connecting to $protocol://$host:$port"; } connect; # http://localhost:8080 connect("example.com"); # http://example.com:8080 connect("example.com", port => 443, ssl => ); # https://example.com:443
Type Constraints
You can specify the expected type for each parameter:Thesub add( $a, $b --> ) { $a + $b; } say add(3, 7); # 10 # say add(3, "7"); # ERROR: Type check failed for parameter '$b'
--> arrow specifies the return type.
Common type constraints:
sub process( $text) { ... } # must be a string sub calculate( $x) { ... } # must be a floating point number sub count( $n) { ... } # must be an integer sub check( $flag) { ... } # must be True or False sub handle( $thing) { ... } # anything goes (this is the default)
Subset Types
You can create custom type constraints withsubset:
subset PositiveInt of where * > 0; subset NonEmpty of where *.chars > 0; sub repeat( $text, PositiveInt $times) { say $text x $times; } repeat("ha", 3); # hahaha # repeat("ha", -1); # ERROR: Type check failed
The Return Type Arrow
You can declare what type a sub returns:sub is-even( $n --> ) { $n %% 2; } say is-even(4); # True say is-even(7); # False
Multi Subs
Here is where Raku gets really interesting.multi sub lets you define multiple versions of the same function, each with a different signature. Raku picks the right one at call time:
Multi dispatch works on types, number of arguments, and even value constraints:multi sub greet( $name) { say "Hello, $name!"; } multi sub greet( $count) { say "Hello to $count people!"; } multi sub greet { say "Hello, stranger!"; } greet("Alice"); # Hello, Alice! greet(42); # Hello to 42 people! greet; # Hello, stranger!
This is the classic FizzBuzz problem solved with multi dispatch. Raku tries each candidate in order of specificity (themulti sub fizzbuzz( $n where * %% 15) { "FizzBuzz" } multi sub fizzbuzz( $n where * %% 3) { "Fizz" } multi sub fizzbuzz( $n where * %% 5) { "Buzz" } multi sub fizzbuzz( $n) { $n. } for 1..20 -> $n { say fizzbuzz($n); }
where clauses are more specific than the plain Int match).
Multi Dispatch with Destructuring
You can match on literal values:This reads almost like a mathematical definition.multi sub factorial(0) { 1 } multi sub factorial( $n where * > 0) { $n * factorial($n - 1); } say factorial(5); # 120 say factorial(10); # 3628800
Slurpy Parameters
When you want a sub to accept any number of arguments, use a slurpy parameter:# Slurpy positional (array) sub sum(*@numbers) { @numbers.sum; } say sum(1, 2, 3, 4, 5); # 15 say sum(10, 20); # 30
# Slurpy named (hash) sub log-event($message, *%details) { say $message; for %details.kv -> $k, $v { say " $k: $v"; } } log-event("User login", user => "alice", ip => "192.168.1.1"); # User login # user: alice # ip: 192.168.1.1
Passing Arrays and Hashes
When you pass an array to a function, it flattens into the positional parameters by default. To pass it as a single argument, use a backslash or the$ sigil:
For hashes:sub show-list(@items) { say "Items: @items.join(', ')"; } my @fruits = <apple banana cherry>; show-list(@fruits); # Items: apple, banana, cherry
sub show-config(%conf) { for %conf.sort -> (:$key, :$value) { say "$key = $value"; } } my %settings = port => 8080, host => "localhost"; show-config(%settings);
Anonymous Subs and Blocks
You can create anonymous subroutines and store them in variables:Blocks withmy &doubler = sub ($n) { $n * 2 }; say doubler(21); # 42 # Or more concisely with a block: my &tripler = -> $n { $n * 3 }; say tripler(14); # 42 # Or with WhateverCode: my &quadrupler = * * 4; say quadrupler(10); # 40
-> $param are called pointy blocks. They are everywhere in Raku (loops, map, grep, etc.).
Practical Example
Let us put everything together in a practical example:Output:#!/usr/bin/env raku # Type-constrained helper sub validate-age( $age --> ) { 0 < $age < 150; } # Multi sub for formatting multi sub format-entry( :$name!, :$age! where &validate-age) { "$name (age $age)"; } multi sub format-entry( :$name!) { "$name (age unknown)"; } # Slurpy collector sub build-roster( $team, *@members) { say "Team: $team"; say "=" x 30; for @members.kv -> $i, $member { say " { $i + 1 }. $member"; } say "Total: { @members.elems } members\n"; } # Use multi dispatch my @entries = ( format-entry(name => "Alice", age => 30), format-entry(name => "Bob", age => 25), format-entry(name => "Charlie"), ); build-roster("Engineering", |@entries);
Team: Engineering ============================== 1. Alice (age 30) 2. Bob (age 25) 3. Charlie (age unknown) Total: 3 members