Custom Operators
Raku lets you define your own operators with custom symbols, precedence, and associativity. This is not just syntactic sugar; it is a powerful metaprogramming feature that lets you create domain-specific notation that reads naturally.Defining Infix Operators
An infix operator sits between two operands:You can use any string as an operator name:sub infix:<plus>($a, $b) { $a + $b } say 3 plus 5; # 8
sub infix:<avg>($a, $b) { ($a + $b) / 2 } say 10 avg 20; # 15
Unicode Operators
Raku supports Unicode in operator names, which can make mathematical code beautiful:sub infix:<+++>($a, $b) { $a + $b + 1 } say 3 +++ 5; # 9 # Cross product for 2D vectors sub infix:<cross>(@a, @b) { @a[0] * @b[1] - @a[1] * @b[0] } say [3, 4] cross [1, 2]; # 2
Prefix and Postfix Operators
Prefix operators come before their operand:Postfix operators come after:sub prefix:<not>($x) { !$x } say not ; # False say not ; # True sub prefix:<double>($x) { $x * 2 } say double 21; # 42
sub postfix:<!>($n) { [*] 1..$n } say 5!; # 120 say 10!; # 3628800
Circumfix Operators
Circumfix operators surround their operand:sub circumfix:<[ ]>(*@items) { @items.sort.list } say [3, 1, 4, 1, 5]; # This is array syntax, would need different symbols sub circumfix:<{{ }}>(*@items) { @items.sort.list } say {{3, 1, 4, 1, 5}}; # (1 1 3 4 5) -- sorted
Postcircumfix Operators
These are operators like array indexing:class Matrix { has @.data; has $.rows; has $.cols; } sub postcircumfix:<[ ]>(Matrix $m, @index) { $m.data[@index[0] * $m.cols + @index[1]] }
Setting Precedence
Useis tighter, is looser, or is equiv to control precedence:
# Same precedence as multiplication sub infix:<dot>(@a, @b) is equiv(&infix:<*>) { [+] @a Z* @b } say [1,2,3] dot [4,5,6]; # 32 # Tighter than addition sub infix:<choose>($n, $k) is tighter(&infix:<+>) { ([*] ($n - $k + 1)..$n) / ([*] 1..$k) } say 10 choose 3; # 120 say 10 choose 3 + 1; # 121 (choose binds tighter, so: (10 choose 3) + 1)
Setting Associativity
Control how operators chain:# Left-associative (default): a op b op c = (a op b) op c sub infix:<then>($a, $b) is assoc<left> { "$a -> $b" } say "a" then "b" then "c"; # a -> b -> c # Right-associative: a op b op c = a op (b op c) sub infix:<arrow>($a, $b) is assoc<right> { "$a -> $b" } say "a" arrow "b" arrow "c"; # a -> b -> c # Non-associative: a op b op c is a compile error sub infix:<cmp-to>($a, $b) is assoc<non> { $a <=> $b } # say 1 cmp-to 2 cmp-to 3; # Compile error!
Practical Example: Unit Conversions
sub postfix:<kg>($n) { $n } sub postfix:<lb>($n) { $n * 0.453592 } sub postfix:<oz>($n) { $n * 0.0283495 } sub postfix:<km>($n) { $n } sub postfix:<mi>($n) { $n * 1.60934 } sub postfix:<ft>($n) { $n * 0.0003048 } say 150lb; # 68.0388 (in kg) say 100mi; # 160.934 (in km) say 6ft; # 0.0018288 (in km) # Comparison works naturally say 10km > 6mi; # True (10 > 9.656)
Practical Example: String Operators
# Repeat with a visual operator sub infix:<x*>($str, $n) { $str x $n } say "ha" x* 3; # hahaha # String contains sub infix:<contains>($haystack, $needle) { $haystack.contains($needle) } say "hello world" contains "world"; # True # String between sub infix:<between>( $str, @bounds) { $str gt @bounds[0] && $str lt @bounds[1] } say "dog" between <cat fish>; # True (alphabetically between)
Practical Example: Pipe Operator
Create a Unix-style pipe for function composition:sub infix:<|>>($data, &func) is assoc<left> { func($data) } my $result = " Hello, World! " |> *.trim |> *.lc |> *.words |> *.elems; say $result; # 2
Practical Example: Null-Coalescing
sub infix:<???>($a, $b) { $a.defined ?? $a !! $b } my $name = ??? "Anonymous"; say $name; # Anonymous my $port = 8080 ??? 3000; say $port; # 8080
Overloading Existing Operators
You can add multi candidates to existing operators for new types:class Vector { has @.components; method () { "<{@!components.join(', ')}>" } } multi sub infix:<+>(Vector $a, Vector $b --> Vector) { Vector.new(components => ($a.components Z+ $b.components).list) } multi sub infix:<*>(Vector $v, Numeric $n --> Vector) { Vector.new(components => $v.components.map(* * $n).list) } multi sub prefix:<->(Vector $v --> Vector) { Vector.new(components => $v.components.map(-*).list) } my $v1 = Vector.new(components => [1, 2, 3]); my $v2 = Vector.new(components => [4, 5, 6]); say $v1 + $v2; # <5, 7, 9> say $v1 * 3; # <3, 6, 9> say -$v1; # <-1, -2, -3>
Guidelines
- Use custom operators sparingly. They are powerful but can make code hard to read for others.
- Prefer standard function/method names for general-purpose code.
- Custom operators shine in domain-specific code (math, physics, DSLs) where notation matters.
- Always document what your operators do and why they exist.
- Set precedence and associativity explicitly to avoid surprises.
Custom operators show Raku's philosophy of giving the programmer maximum expressiveness. When used well, they can make domain-specific code read almost like the notation experts in that domain already use.