Lazy Lists and Sequences
Raku embraces laziness. Rather than computing every element upfront, lazy lists generate values on demand. This lets you work with infinite sequences, process huge datasets without blowing up memory, and write elegant functional code.Lazy Ranges
Ranges in Raku are lazy by default when they are unbounded:Themy @infinite = 1 .. *; # Infinite range -- lazy say @infinite[0..4]; # (1 2 3 4 5) say @infinite[99]; # 100 my @finite = 1 .. 1000; # Finite range -- also lazy until reified
* (Whatever) makes a range infinite. No memory is allocated for elements that have not been accessed yet.
Checking Laziness
You can check whether a list is lazy with the.is-lazy method:
say (1..*).is-lazy; # True say (1..100).is-lazy; # False say (1..*).grep(*.is-prime).is-lazy; # True
The Sequence Operator (...)
The... operator is Raku's most versatile tool for creating sequences. It infers patterns:
The# Arithmetic sequences say (1, 3, 5 ... 19); # (1 3 5 7 9 11 13 15 17 19) say (0, 10, 20 ... 100); # (0 10 20 30 40 50 60 70 80 90 100) # Geometric sequences say (1, 2, 4 ... 256); # (1 2 4 8 16 32 64 128 256) say (1000, 100, 10 ... 0.01);# (1000 100 10 1 0.1 0.01) # Fibonacci say (1, 1, *+* ... *)[^10]; # (1 1 2 3 5 8 13 21 34 55) # Custom formula say (1, { $_ * 2 + 1 } ... *)[^8]; # (1 3 7 15 31 63 127 255)
... operator takes seed values and a generator (or infers one from the pattern), producing a lazy sequence.
Infinite Sequences
Because sequences are lazy, infinite ones are perfectly fine:You just take what you need with# All natural numbers my @nat = 0, 1, 2 ... *; # All squares my @squares = (0, 1, 4 ... *); # Hmm, this won't infer correctly my @squares2 = (^Inf).map(* ** 2); # Better # All Fibonacci numbers my @fib = 1, 1, *+* ... *; say @fib[^15]; # (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610) # Powers of 2 my @pow2 = 1, 2, 4 ... *; say @pow2[^10]; # (1 2 4 8 16 32 64 128 256 512)
[^N] or .head(N).
gather/take
Thegather/take construct is Raku's coroutine-style generator. It creates a lazy list where you explicitly yield values:
Themy @evens = gather { my $n = 0; loop { take $n; $n += 2; } }; say @evens[^10]; # (0 2 4 6 8 10 12 14 16 18)
gather block runs lazily. Execution pauses at each take until the next value is requested.
Practical gather/take
Here is a more practical example, generating a filtered stream:And a tree traversal:sub primes() { gather { for 2..* -> $candidate { take $candidate if $candidate.is-prime; } } } say primes()[^20]; # (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)
class Node { has $.value; has Node $.left; has Node $.right; } sub inorder(Node $n) { gather { if $n.left { take $_ for inorder($n.left) } take $n.value; if $n.right { take $_ for inorder($n.right) } } } my $tree = Node.new( value => 4, left => Node.new( value => 2, left => Node.new(value => 1), right => Node.new(value => 3), ), right => Node.new(value => 5), ); say inorder($tree); # (1 2 3 4 5)
Lazy Operations Chain
Most list operations preserve laziness:This pipeline does not compute a single value untilmy @result = (1..*) .grep(*.is-prime) # Still lazy .map(* ** 2) # Still lazy .head(10); # Takes first 10 say @result; # (4 9 25 49 121 169 289 361 529 625) -- first 10 squared primes
.head(10) pulls them through.
Controlling Reification
Sometimes you need to force a lazy list to fully evaluate (reify):Be careful: callingmy @lazy = (1..*).grep(*.is-prime).head(100); # .eager forces full evaluation my @eager = @lazy.eager; # .cache ensures values are computed once and stored my @cached = (1..*).grep(*.is-prime).head(100).cache;
.eager on an infinite list will hang forever.
Lazy File Processing
Laziness is great for processing large files line by line:# lines() is lazy -- reads one line at a time for "large-file.txt"..lines -> $line { say $line if $line.contains("ERROR"); } # Process a huge file without loading it all into memory my $error-count = "large-file.txt"..lines.grep(/ERROR/).elems; say "Found $error-count errors";
Zipping Lazy Sequences
You can combine lazy sequences:my @letters = 'a' .. 'z'; my @numbers = 1 .. *; for @letters Z @numbers -> ($l, $n) { say "$l => $n"; } # a => 1, b => 2, ... z => 26 # Stops naturally when @letters is exhausted
Tips
- Default to lazy. Let Raku evaluate only what is needed.
- Use
gather/takewhen you need complex generation logic that does not fit a simple formula. - Remember that
.elemson a lazy list will try to reify the whole thing. Use.head(N)or[^N]to limit. - The sequence operator
...is incredibly flexible. If you can state the pattern with 2-3 seed values, Raku will figure out the rest.
Laziness is a fundamental part of Raku's design philosophy. It makes your code more memory-efficient and often more expressive.