NAME

Seq - A lazy sequence implementation

SYNOPSIS

A sequence is a builder/generator for iterators. You use a sequence and describe operations on a sequence. A sequence can then be asked to return an iterator that do the actual work.

The idea is that you never use the iterators directly. You only use a sequence. The iterator behind it is an implementation detail.

The advantage is that you have a high-level API with map, filter, fold and so on. It combines all the functionality you see from map, grep, List::Util, ...

But it does it lazily instead of computing everything at once. Thus it can provide immidiat results (when possible) and/or use less memory. Sometimes even saving computation time. It can work with infinity sequences and really large inputs.

Everything that is an iterator can potentially used with this API. This includes file-handles, sockets, pipes and whatever you can think of.

Once you have defined a sequence. You can execute the sequence as often you want on whatever data you give it. From its usage it looks like an immutable iterator.

# always represents the range from 1 to 100.
my $range = Seq->range(1,100);

# prints numbers 1 to 100
$range->iter(sub($x) { say $x });

# prints numbers 1 to 100 again ...
$range->iter(sub($x) { say $x });

At the moment Documentation is lacking, but the source-code is well-documented including the test-files. Maybe you want to look at the test-files until I have written more documentation. The API is not fully stable at the moment.

use v5.36;
use Seq;

# Fibonacci Generator
my $fib =
    Seq->concat(
        Seq->wrap(1,1),
        Seq->unfold([1,1], sub($state) {
            my $next = $state->[0] + $state->[1];
            return $next, [$state->[1],$next];
        })
    );

# prints: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
$fib->take(20)->iter(sub($x) {
    say $x;
});

# Represents all possible combinations
# [[clubs => 7], [clubs => 8], [clubs => 9], ...]
my $cards =
    Seq::cartesian(
        Seq->wrap(qw/clubs spades hearts diamond/),
        Seq->wrap(qw/7 8 9 10 B D K A/)
    );

use Path::Tiny qw(path);
# get the maximum id from test-files so far
my $maximum_id =
    Seq
    # get all files from 't' folder
    ->wrap( path('t')->children )
    # get basename of each file
    ->map(  sub($x) { $x->basename })
    # extract all numbers from test files. returns matches as array
    ->regex_match( qr/\A(\d+) .* \.t\z/xms, [1])
    # get the first entry of each array (there is only one)
    ->fsts
    # get the maximum number, or -1 if sequence is empty
    ->max(-1);

CONCEPT

Functions are divided into CONSTRUCTORS, METHODS, CONVERTERS.

CONSTRUCTORS

All constructor functions are directly called from the Seq module. Like Seq->init( ... ). The return a Seq.

METHODS

All methods can be called like a method on a Seq or still be called in a functional-style. Both styles are supported. In the documentation only the functional style how it is defined in Perl is shown.

$seq->length();
Seq::length($seq);

CONVERTERS

Calling style is the same as METHODS. They are called CONVERTERS because they usually return something different than a sequence again. For example $seq->map(...) returns a new sequence, but $seq->max will return a maximum value out of a sequence that can be potentially something different than a sequence.

IMPORTING / EXPORTING / LOADING

This module is automatically loaded when you load Sq. The module is available under Seq.

CALLING STYLE

Describe functional and chaining style here.

CONSTRUCTORS

This module uses functional-programming as the main paradigm. Functions are divided into constructors, methods and converters.

Constructor create a sequence. Methods operate on sequences and return another new sequence. Converter transforms a sequence to some other data-type.

Methods are called methods for convenience, but no object-orientation is involved. Perls OO capabilities are only used as a chaning mechanism.

Constructors must be called with the Package name. Functions that operate on Sequences can either be called as a method or directly from the Package.

my $range =
    Seq
    ->wrap(1,2,3)
    ->append(Seq->wrap(4,5,6));

or

my $range =
    Seq::append(
        Seq->wrap(1,2,3),
        Seq->wrap(4,5,6),
    )

Seq->empty() : $seq

Returns an empty sequence. Useful as an initial state or as a starting point.

my $seq = Seq->empty->append( $another_seq );

Seq->range($start, $stop) : $seq

Returns a sequence from $start to $stop. Range can also be backwards. $start and $stop are inclusive.

my $seq = Seq->range(1, 5); # 1,2,3,4,5
my $seq = Seq->range(5, 1); # 5,4,3,2,1
my $seq = Seq->range(1, 1); # 1

Seq->range_step($start, $step, $stop) : $seq

Like Seq->range but let's you specify a step. Both values are inclusive and also supports steps with floating-point. Consider floating-point inaccurassy.

my $seq = Seq->range(1,2,10)  # [1,3,5,7,9]
my $seq = Seq->range(10,2,1)  # [10,8,6,4,2]
my $seq = Seq->range(1,0.2,2) # [1,1.2,1.4,1.6,1.8,2]
my $seq = Seq->range(1,0.2,2) # [2,1.8,1.6,1.4,1.2,1]

Seq->new(...) : $seq

Just takes whatever you pass it to, and puts it in a sequence. This should be your primarily way to create a sequence with values.

my $seq = Seq->new(qw/Hello World/); # "Hello", "World"
my $seq = Seq->new(1 .. 10);         # AVOID this, use Seq->range(1, 10) instead.
my $seq = Seq->new(@array);

Seq->wrap(...) : $seq

Same as Seq->new.

my $seq = Seq->wrap(qw/Hello World/); # "Hello", "World"
my $seq = Seq->wrap(1 .. 10);         # AVOID this, use Seq->range(1, 10) instead.
my $seq = Seq->wrap(@array);

Seq->concat(@sequences) : $seq

Takes multiple *Sequences* and returns a single flattened sequence.

# 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0
my $seq = Seq->concat(
    Seq->new(0),
    Seq->range(1, 5),
    Seq->range(5, 1),
    Seq->new(0),
);

Seq->always($x) : $seq

Returns an infinite sequence that always returns $x as it's value.

my $seq    = Seq->always(10);
my $array1 = $seq->take(5)->to_array; # [10, 10, 10, 10, 10]
my $array2 = $seq->take(3)->to_array; # [10, 10, 10]

Seq->replicate($count, $x) : $seq

Creates a sequence representing $x $count times.

# A sequence representing 100_000 times the value 1
my $seq = Seq->replicate(100_000, 1);

Seq->unfold($state, $f) : $seq

unfold is like writing a loop to generate values. But instead that the whole loop is completely run to an end it just runs the function $f with the current $state to generate the next value in a sequence and which next $state should be used for the next call. It runs as long you try to query data from it or the sequence indicates an end by returning undef.

my $seq = Seq->unfold(0, sub($x) {   # $x is 0 on first invocation
    return $x, $x+1;                 # return $x, and uses $x+1 as the next state.
});

my $first100 = $seq->take(100); # represents a sequence containing 0-99
my $first500 = $seq->take(500); # represents a sequence containing 0-499

Seq->init($count, $f) : $seq

Generates a maximum of $count items and passes the current index to the function $f that performs the generation for that entry.

my $seq = Seq->init(10, sub($idx) { $idx     }); # seq { 0 .. 9 }
my $seq = Seq->init(10, sub($idx) { $idx * 2 }); # seq { 0,2,4,6,8,10,12,14,16,18 }

Seq->from_array($array) : $seq

Generates a sequence from an array reference. This operation is very efficent as the array is not copied. When you created an array (in a function) but want to return it as a sequence this is the function you maybe wanna use. This way you also can return an array as an immutable sequence, at least as long the original array reference gets out of scope. Otherwise changing the original mutable array will yield new/updated values.

my $array = [1 .. 10];
my $seq   = Seq->from_array($array);

my $sumA = $seq->sum; # 55

push @$array, 10;
my $sumB = $seq->sum; # 65

Seq->from_hash($hash, $f) : $seq

Turns a hash-ref into a sequence. When data is queried than every key,value pair is passed to $f that then can return a value that is used as the sequence item. The hash-ref is not copied like in Seq->from_array. This means changing the hashref and quering the sequence again will get updated values.

my $hash = {
    foo => 1,
    bar => 2,
    baz => 3,
};

my $keys   = Seq->from_hash($hash, sub($key,$value) { $key   });
my $values = Seq->from_hash($hash, sub($key,$value) { $value });

my $ka  = $keys->to_array; # ["foo", "bar", "baz"]
my $sum = $values->sum;    # 6

$hash->{maz} = 4;

my $ka  = $keys->to_array; # ["foo", "bar", "baz", "maz"]
my $sum = $values->sum;    # 10

Seq->from_sub($f) : $seq

The foundation from that all sequences are created. You can turn any function that returns a function into a sequence. The inner function must return undef to indicate the end of the sequence. As soon the inner function returns undef you can be sure that the inner function is never called again and memory should be released.

For example an upto function that just returns the value from 0 upto $x could be implemented this way.

sub upto($x) {
    return Seq->from_sub(sub {
        # Here comes the Initial State of the iterator
        my $current = 0;

        return sub {
            if ( $current <= $x ) {
                return $current++;
            }
            return undef;
        };
    });
}

# A sequence from 0-100
my $seq = upto(100);

# [0, 2, 4, 6, 8]
my $first5even =
    $seq
    ->filter(sub($x) { $x % 2 == 0 })
    ->take(5)
    ->to_array;

METHODS

Implemented, but not documented yet:

append, map, bind, flatten cartesian, join, merge, select*, choose, mapi, filter, take, skip, indexed, distinct, distinct_by, iter, do, rev

* will maybe change

copy($seq) : $seq

Copies a sequence, but because how sequence works it basically does nothing. It is just here for API compatibility with other modules like Array::copy or List::copy.

my $same = $seq->copy;

append($seqA, $seqB) : $seq

returns a new sequence where both sequences are appended into a single one. When you want to append multiple sequences it is usually better to use Seq->concat instead.

my $new    = $seq->append($other_seq);
my $upto10 = Seq->range(1,5)->append( Seq->range(6,10) ); # seq { 1 .. 10 }

map($seq, $f) : $seq

returns a new sequence where every value is mapped with the function $f.

my $seq = Seq->range(1,10);

my $double  = $seq->map(sub($x) { $x * 2  }); # seq { 2,4,6,8,10,12,14,16,18,20 }
my $squared = $seq->map(sub($x) { $x * $x }); # seq { 1,4,9,16,25,36,49,64,81,100 }

map2($seqA, $seqB, $f) : $seq

Goes through two sequences at once, and passes both values to $f. $f then generates the new value used in the sequence. Stops as soon one sequence finishes.

# [1+5, 2+4, 3+3, 4+2, 5+1]
my $additions =
    Seq::map2(
        Seq->new(1,2,3,4,5,6),
        Seq->new(5,4,3,2,1),
        sub($x,$y) { $x + $y });

# [1+5, 2+4, 3+3, 4+2, 5+1]
my $additions =
    Seq->new(1,2,3,4,5,6)->map2(
        Seq->new(5,4,3,2,1),
        sub($x,$y) { $x + $y });

mapi($seq, $f) : $seq

Similar to map but the function $f additionally receives the current index of the item it processes.

# seq { [5,0], [6,1], [7,2], [8,3], [9,4], [10,5] }
my $seq =
    Seq
    ->range(5,10)
    ->mapi(sub($x,$idx) { [$x,$idx] });

filter($seq, $predicate) : $seq

returns a new sequence with the elements filtered by $predicate. A predicate function is a function that returns a boolish value. When it returns a truish value then the element is kept in the sequence. Similar zu perl built-in grep for an array.

# contains only even numbers
my $seq = $seq->filter(sub($x) { $x % 2 == 0 });

choose($seq, $f_opt) : $seq

Similar to map as it executes $f_opt function on every element of the sequence. $f_opt is supposed to return an optional value that indicates if that element should be picked or not. When it returns Some value it will use the value, and None values are skipped.

This function is a combination of map followed by filter in a single call.

# seq { 2,4,6,8,10,12,14,16,18,20 }
my $seq =
    Seq
    ->range(1,10)
    ->map(   sub($x) { $x * 2      })
    ->filter(sub($x) { $x % 2 == 0 });

# seq { 2,4,6,8,10,12,14,16,18,20 }
my $seq =
    Seq
    ->range(1,10)
    ->choose(sub($x){
        my $double = $x * 2;
        return $double % 2 == 0 ? Some($double) : None;
    });

bind($seq, $f) : $seq

Similar to map as every value of the sequence is passed to function $f. But $f is supposed to return a new sequence again. Instead of returning a sequence of sequence it just returns them as a flattened sequence.

# seq { 0,1, 0,1,2, 0,1,2,3, 0,1,2,3,4, 0,1,2,3,4,5 }
my $nums =
    Seq
    ->range(1,5)
    ->bind(sub($x) { Seq->range(0,$x) });

Think of bind as a way to just iterate through every item of a sequence, but the function you provide to bind returns a sequence again. But instead of getting a sequence of sequence you get just a single sequence flattened.

This operation is powerful as you can implement a lot of other functions just with bind. This operation is also for example called then in JavaScript/promises API. It allows you to chain operations. Like an async operation that returns an async again.

It is called bind because it resembles binding a value. Consider the code

my $x = 10;

then you would bind the value 10 to the variable $x. When you loop through an array.

for my $x ( @array ) {
    ...
}

Then you could say that you bind every value inside of @array to $x.

$seq->bind(sub($x) {
    ...
});

Here we also bind every value of a sequence to $x. The binding is just reversed in the sense that instead of having something on the right and binding it to the variable on the left, we just bind it from left-to-right.

You could for example implement Seq::map the following way. Even though this is not implemented this way because of performance reasons.

sub seq_map($seq, $f) {
    $seq->bind(sub($x) {
        Seq->new($f->($x));
    });
}

flatten($seq_of_seq) : $seq

returns a new sequence that flattens a sequence of sequences into a single sequence. You typically can get a sequence of sequence when you use map with a function that returns a sequence. Instead of calling flatten on that result use bind instead of map.

# seq { seq {1,2,3}, seq{4,5,6}, seq{7,8,9} }
my $nums = Seq->new(
    Seq->range(1,3),
    Seq->range(4,6),
    Seq->range(7,9),
);

# seq { 1,2,3,4,5,6,7,8,9 }
my $flatten = $nums->flatten;

flatten_array($seq_of_array) : $seq

returns a new sequence that flattens a sequence of arrays into a single sequence.

# seq { 1 .. 9 }
my $nums = Seq->new([1,2,3], [4,5,6], [7,8,9])->flatten;

cartesian($seqA, $seqB) : $seq_of_array

returns a new sequence containing the cartesian product of both sequences. The cartesian product is every item in $seqA combined with every item in $seqB.

# cartesian product of two arrays
my @cartesian;
for my $a ( @as ) {
    for my $b ( @bs ) {
        push @cartesian, [$a, $b];
    }
}

# cartesian product of two sequences
$a->cartesian($b)->iter(sub($pair) {
    my ($a, $b) = @$pair;
    printf "A=%s B=%s\n", $a, $b;
});

take($seq, $amount) : $seq

returns a new sequence that only takes $amount from its previous sequence.

# seq { 1 .. 10 }
my $seq = Seq->range(1,100)->take(10);

take_while($seq, $predicate) : $seq

returns a new sequence and picks every element from the sequence as long $predicate returns a truish value.

# seq { 20,10,3,70 }
my $seq =
    Seq
    ->new(20,10,3,70,105,40,200)
    ->take_while(sub($x) { $x < 100 ? 1 : 0 });

skip($seq, $amount) : $seq

returns a new sequence, but skips $amount items.

# seq { 4,5,6,7,8,9,10 }
my $seq = Seq->range(1,10)->skip(3);

skip_while($seq, $predicate) : $seq

returns a new sequence by skipping all values at the begging as long $predicate returns a truish value. After that, returns all values as-is.

# seq { 70,105,40,200 }
my $seq =
    Seq
    ->new(20,10,3,70,105,40,200)
    ->skip_while(sub($x) { $x < 50 ? 1 : 0 });

indexed($seq) : $seq_of_array

returns a new sequence that puts the index and on element together. If you just need to process an item with the index you should use mapi or iteri instead.

# seq { [0,10], [1,11], [2,12], [3,13], [4,14], [5,15] }
my $seq = Seq->range(10,15)->indexed;

distinct($seq) : $seq

returns a new sequence where every element will only appear once. Internally a hash is used for seen elements. That means it only works properly when elements can be converted to a string. Otherwise use distinct_by.

# seq { 1,2,3,4,5,6,7,10,8 }
my $seq =
    Seq
    ->new(1,2,3,3,4,2,1,5,6,5,4,7,10,8)
    ->distinct;

distinct_by($seq, $f) : $seq

Same as distinct but uses function $f to generate the key for an item to be used as the seen key.

my $data = Seq->new(
    {id => 1, name => "Foo"},
    {id => 2, name => "Bar"},
    {id => 3, name => "Baz"},
    {id => 1, name => "Foo"},
);

my $seq = $data->distinct_by(sub($x) { $x->{id} });
# seq {
#     {id => 1, name => "Foo"},
#     {id => 2, name => "Bar"},
#     {id => 3, name => "Baz"},
# }

fsts($seq_of_array) : $seq

returns a new sequence that only picks the first element of each array.

# seq { "Hearth","Hearth","Spades" }
my $strings =
    Seq->new(
        ["Hearth", 1],
        ["Hearth", 2],
        ["Spades", 3],
    )
    ->fsts;

fsts($seq_of_array) : $seq

returns a new sequence that only picks the second element of each array.

# seq { 1,2,3 }
my $nums =
    Seq->new(
        ["Hearth", 1],
        ["Hearth", 2],
        ["Spades", 3],
    )
    ->snds;

zip($seqA, $seqB) : $seq_of_array

Combines two sequences into a single sequence by always combining the next element of both sequences. Stops as soon one sequence finish.

# seq { ["foo",1], ["bar",1], ["baz",1] }
my $mapping = Seq->zip(
    Seq->new(qw/foo bar baz/),
    Seq->always(1),
);

rev($seq) : $seq

returns a new sequence that reverses the sequence. Consider that reversing is only possible when all elements of a sequence are iterated. Internally this function builds a stack from a sequence and than pops every element until done. Calling this method on a infinite sequence will probably hang/crash your program/computer.

# seq { 10,9,8,7,6,5,4,3,2,1 }
my $seq = Seq->range(1,10)->rev;

sort($seq, $comparer) : $seq

Sorts a sequence by using $comparer function. Consider that sorting is only possible when all elements of a sequence are known. Thus this method must build an array from all elements of a sequence and than sorts it.

# seq { 1,2,5,8,10,20,30 }
my $sorted_nums =
    Seq
    ->new(1,30,5,2,8,10,20)
    ->sort(sub($x,$y) { $x <=> $y });

sort_by($seq, $comparer, $get_key) : $seq

Does a Schwartzian Transformation by first passing every element to the function $get_key that returns the key used in sorting. Than uses $comparer to sort by the generated keys. Returns the original entries sorted by its keys.

# seq { "a","as","hello","between" }
my $seq =
    Seq
    ->new("hello", "a", "between", "as")
    ->sort_by(
        sub($x,$y){ $x <=> $y },  # comparer, sort by number
        sub($x)   { length $x },  # get_key that uses string length
    );

group_fold

TODO

group_by

TODO

cache($seq) : $seq

runs a sequence at this point and builts an array. Then returns a new sequence from that array. This can be good when you have a long chain of transformations and at certain points you want to compute the values instead of recomputing it again and again. It's also a good candidate when the sequence operates on mutable data. Calling cache still only makes sense when you want to use the final sequence and do additional operations on it. Otherwise you just can call to_array.

my $evens =
    Seq
    ->range(1,10_000)
    ->filter(sub($x) { $x % 2 == 0 })
    ->cache;

# $evens is only computed once, but also uses more memory.
my $doubled = $evens->map(sub($x) { $x * 2  });
my $squared = $evens->map(sub($x) { $x * $x });

regex_match($seq_of_strings, $regex) : $seq_of_array

Matches a $regex against every element of a sequence that is supposed to be a string. When a regex matches against the string it extracts all captures into an array.

my $lines = Seq->new(
    '2023-11-25T15:10:00',
    '2023-11-20T10:05:29',
    'xxxx-xx-xxT00:00:00',
    '1900-01-01T00:00:01',
    '12345678901234567890',
);

my $matches = $lines->regex_match(qr/
    \A
        (\d\d\d\d) - (\d\d) - (\d\d)  # Date
    T                                 # T
        (\d\d) : (\d\d) : (\d\d)      # Time
    \z/xms
)->to_array;

# $matches
# [
#     [qw/2023 11 25 15 10 00/],
#     [qw/2023 11 20 10 05 29/],
#     [qw/1900 01 01 00 00 01/],
# ],

windowed($seq, $window_size) : $seq_of_array

TODO

intersperse($seq, $separator) : $seq

returns a new sequence with $sepator injected between every element.

# seq { 1,'+',2,'+',3,'+',4,'+',5 }
my $seq = Seq->range(1,5)->intersperse('+');

infinity($seq) : $seq

Repreats a sequence up to infinity.

# seq { 1,2,3,1,2,3,1,2,3,1,2,3,1, ... }
my $seq = Seq->range(1,3)->infinity;

repeat($seq, $cont) : $seq

Repeats a whole sequence a given amount.

# seq { 1,2,3, 1,2,3, 1,2,3 }
my $seq = Seq->range(1,3)->repeat(3);

CONVERTERS

Converts are normal methods that also can be called in a functional-style. They are called Converters because they usually transform a Sequence into something different than a sequence.

Converters usually needs to iterate some or all values of a sequence to produce its output. Calling some of them on an infinite sequence can maybe cause unwanted problems.

to_array($seq, $count=undef) : $array

Converts the whole sequence into an array. This causes the sequence to compute things. Optionally you can pass in a $count of how many max items you want to take from the sequence. When no $count or undef or not a number is passed, then all items from the sequence are taken.

When you just want to iterate through all items, instead of calling to_array and then iterate through that array just use iter instead.

# those are the same
$seq->take(10)->to_array == $seq->to_array(10);

my $array = $seq->to_array;
my $array = $seq->to_array(10);

join($seq, $separator) : $string

Concatenates all elements of a sequence to a string with $separator.

my $str = Seq->new("foo", "bar)->join("-"); # "foo-bar"

first($seq) : $opt

Returns first element of seq as an Option. Returns None when the sequence is empty.

my $opt = $seq->first;

my $opt = Seq->range(1,10)->first;       # Some(1)
my $opt = Seq->empty      ->first;       # None
my $x   = Seq->empty      ->first->or(0) # 0

last($seq) : $opt

Returns last element of seq as an Option. Returns None when the sequence is empty.

my $opt = $seq->last;

my $opt = Seq->range(1,10)->last;       # Some(10)
my $opt = Seq->empty      ->last;       # None
my $x   = Seq->empty      ->last->or(0) # 0

min($seq) : $opt_number

Returns the smallest number in a sequence as an optional. When the sequence is empty a None is returned.

min uses numeric < operator to compare items. So any object that overloads < will also work.

my $opt = Seq->range(100,200)->min; # Some(100)
my $opt = Seq->empty->min;          # None

min_by($seq_x, $f_number) : $opt_x

Iterates through a sequence to find the minimum element $x. While iterating my $num = $f_number->($x) is called to get the $num that is used for comparision. The $x with the minimum $num is returned as a result.

Returns an optional because in the case of an empty sequence there can be no min value and None is being returned.

min_by uses numeric < operator to compare items. So any object that overloads < will also work.

my $data = Seq->new(
    { id => 1, name => 'A' },
    { id => 2, name => 'B' },
    { id => 3, name => 'C' },
);

my $opt = $data->min_by(key 'id'); # Some({ id => 1, name => 'A' })

The function key is automatically loaded/imported by loading Sq. key generates a function that selects a specific entry from a hash.

max($seq) : $opt_number

Returns the maximum number in a sequence as an optional. When the sequence is empty a None is returned.

max uses numeric > operator to compare items. So any object that overloads > will also work.

my $opt = Seq->range(100,200)->max; # Some(200)
my $opt = Seq->empty->max;          # None

max_by($seq_x, $f_number) : $opt_x

Iterates through a sequence to find the maximum element $x. While iterating my $num = $f_number->($x) is called to get the $num that is used for comparision. The $x with the maximum $num is returned as a result.

Returns an optional because in the case of an empty sequence there can be no max value and None is being returned.

max_by uses numeric > operator to compare items. So any object that overloads > will also work.

my $data = Seq->new(
    { id => 1, name => 'A' },
    { id => 2, name => 'B' },
    { id => 3, name => 'C' },
);

my $opt = $data->max_by(key 'id'); # Some({ id => 3, name => 'C' })

The function key is automatically loaded/imported by loading Sq. key generates a function that selects a specific entry from a hash.

min_str($seq) : $opt

The same as min but uses string comparison lt to compare items. Any object that overloads lt will also work.

my $opt = Seq->new(qw/c b a/)->min_str; # Some('a')
my $opt = Seq->empty->min_str;          # None

min_str_by($seq_x, $f_str) : $opt_x

Same as min_by but uses string comparison lt to compare items. Any object that overloads lt will also work.

my $data = Seq->new(
    { id => 1, name => 'A' },
    { id => 2, name => 'B' },
    { id => 3, name => 'C' },
);

my $opt = $data->min_str_by(key 'name'); # Some({ id => 1, name => 'A' })

max_str($seq) : $opt

Same as max but uses string comparison gt to compare items.

my $opt = Seq->new(qw/c b a/)->max_str; # Some('c')
my $opt = Seq->empty->max_str;          # None

max_str_by($seq_x, $f_str) : $opt_x

Same as max_by but uses string comparison gt to compare items.

my $data = Seq->new(
    { id => 1, name => 'A' },
    { id => 2, name => 'B' },
    { id => 3, name => 'C' },
);

my $opt = $data->max_str_by(key 'name'); # Some({ id => 3, name => 'C' })

as_hash($seq) : $hash

Turns a sequence into a Sq Hash by expecting that the sequence contains key/value pairs. Similar as writing my %hash = @list in Perl.

# { foo => 1, bar => 2 }
my $hash = Seq->new(qw/foo 1 bar 2/)->as_hash;

to_hash($seq, $mapper) : $hash

Calls $mapper function for every element in the sequence. Expects that $mapper returns the key and value used to build the final Hash.

# {"Hello" => 5, "World" => 5, "One" => 3, "Two" => 3}
my $hash = Seq->new(qw/Hello World One Two/)->to_hash(sub($key) {
    $key => length $key
});

expand($seq) : ($x,$x,$x,$x,...)

Similar to to_array as that it starts to turn a sequence into an array. But instead of returning it as a blessed Sq Array it returns it as a Perl List in List Context.

Only use this function if you need to pass a list of values to some other function, if you just want to iterate every element use iter, iteri instead. Prefer to_array over expand. When you want to turn a Sequence into a Hash, use as_hash or to_hash instead.

my @array = $seq->expand;
my $array = $seq->to_array();

for my $x ( $seq->expand ) {
    ...
}

# Better
$seq->iter(sub($x) {
    ...
});

fold($seq, $state, $f_state) : $state

fold is like a foreach-loop. You iterate through all items generating a new State. The $f_state is passed one element from the sequence and the latest $state. $f_state then returns the next $state that should be used. Once all elements of the sequence are processed the latest $state is returned.

my $seq = Seq->range(1,1000);
my $sum = $seq->fold(0, sub($x,$state) {
    $state + $x
});

Above you can see that 0 is passed as the starting State. On the first iteration the subroutine gets 0 as $state passed and 1 as the first element of the sequence. It calculates 0 + 1 and returns 1 as the next State.

Then for the next iteration 2 for $x and 1 for $state is used. It returns 1 + 2 as the next $state and continues until all elements of the sequence are iterated. The last calculated $state is returned as the final result of fold.

You can compare it to a foreach-loop because what you define as $state is usually what you would define outside of a loop. Consider the following example with an Array as comparison to understand what fold does.

my @seq   = (1 .. 1000);
my $state = 0;
for my $x ( @seq ) {
    $state = $state + $x;
}
return $state;

fold_mut($seq, $state, $f_state) : $state

fold_mut is an optimization of fold. fold works by computing a new $state on every call. Every time the $f_state function is executed the result of this call is assigned to $state. In a fully immutable programming environment this is usually fine and the only way to create immutable-data.

Perl on the other hand is not an immutable language, don't provide immutable data-types by default and because of the lack of a JIT or a compiler with a dozens of compiler optimizations is in general slower. Because of that it is sometimes helpful to operate on mutable data as an optimization.

fold_mut works the same as fold, but it expects that $state is mutated during every iteration. After all operations are finished, $state is still returned.

# this transforms a sequence into an array
my $array = Seq->range(1,1000)->fold_mut([], sub($x,$array) {
    push @$array, $x;
    return;
    # the explicit return is not needed.It just shows that the return
    # value of this lambda is not used.
});

reduce($seq, $default, $reducer) : $return_of_reducer

In a dynamic-typed language like Perl it is basically the same as fold. The only difference is that no $state is explicitly passed and it just begins calling $reducer with the first two elements of the sequence.

When the sequence only has one element than this element is immediately returned without that $reducer is ever called. It is compareable when you have an empty sequence and use fold, then $state is immediately returned.

But what should be returned when the sequence is empty? Because of this reduce expects an argument $default. Other possibilites would be to throw an exception, return an option or something else.

I highly encourage to better use fold or fold_mut instead of reduce.

my $sum = Seq->range(1,1000)->reduce(0, sub($x,$y)     { $x + $y     });
my $sum = Seq->range(1,1000)->fold(  0, sub($state,$x) { $state + $x });

dump($seq, $inline=60, $depth=0) : $str

Recursivly traverses data-structures and creates a human readable dump from it. Only the first 20 entries are shown.

$inline controls the amount of characters a data-structure is completely inlined into a single string without newlines. The higher the number the more compact your dump will be.

It currently has a bug as it also collaps whitespace in a string and it shouldn't do that. This could be avoided by setting $inline to 0. But consider that dumping in its current form is considered for debugging purposes, not for serializing data.

Currently it is not perfect. It only works with Perl Array/Hash and Sq Array/Hash and the Option type. Sq Array/Hash are just dumped as normal Perl Array/Hash. No other object is being dumped. It also does not dump any other object and has no configuration. Also doesn't detect cyclic data-structures. But for most practical data-structures it works good enough at the moment. Get's improved in the future.

printf "%s\n", $opt->dump;

dumpw($seq, $inline=60, $depth=0) : void

Same as dump but instead of returning the dump as a string, automatically prints it using warn.

$opt->dumpw;
warn $opt->dump, "\n";

SIDE-EFFECTS

iter($seq, $f) : void

Iterates through all elements of a sequence and calling $f for every element. As iter returns nothing it is just used for doing side-effects. Or maybe when you want to iterate through a sequence and build other data. But consider using fold or fold_mut instead. Or any other of the CONVERTER methods.

Seq->range(1,1000)->iter(sub($x) {
    say $x;
});

foreach($seq, $f) : void

Same as iter.

Seq->range(1,1000)->foreach(sub($x) {
    say $x;
});

iteri($seq, $f) : void

Same as iter but $f additionally gets an index passed.

Seq->range(10,15)->iteri(sub($x,$idx) {
    printf "%d: %d\n", $idx, $x;
});

# prints
# 0: 10
# 1: 11
# 2: 12
# 3: 13
# 4: 14
# 5: 15

foreachi($seq, $f) : void

Same as iteri.

Seq->range(10,15)->foreachi(sub($x,$idx) {
    printf "%d: %d\n", $idx, $x;
});

# prints
# 0: 10
# 1: 11
# 2: 12
# 3: 13
# 4: 14
# 5: 15

do($seq, $f) : $seq

A functions like iter starts to iterate all elements of a sequence. do does not. It just allows that a function is called before each element is returned. This is usually useful in debugging. You can easily intercept anywhere between in a sequence.

# [4,16,36,64,100]
my $array =
    Seq
    ->range(1, 1000)
    ->filter(sub($x) { $x % 2 == 0       })
    ->do(    sub($x) { printf "%d\n", $x })
    ->map(   sub($x) { $x * $x           })
    ->to_array(5);

The above code first filters out even numbers, then calculates the square of each number. It only returns the first 5 numbers as an array. But besides doing that, this code will print.

2
4
6
8
10

do only executes for those elements you ask for. If you don't force evaluation somehow through any CONVERTER method, then nothing will happen. Use iter when you want to force evaluation.

doi($seq, $f) : $seq

Same as do but additionally passes an index to the function $f.

# [4,16,36,64,100]
my $array =
    Seq
    ->range(1, 1000)
    ->filter(sub($x)      { $x % 2 == 0                 })
    ->doi(   sub($x,$idx) { printf "$d: %d\n", $idx, $x })
    ->map(   sub($x)      { $x * $x                     })
    ->to_array(5);

Besides returning the array, the code will also print.

0: 2
1: 4
2: 6
3: 8
4: 10

Github

Development project is on Github. https://github.com/DavidRaab/Seq

AUTHOR

David Raab, <davidraab83 at gmail.com>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2023 by David Raab.

This is free software, licensed under:

The MIT (X11) License