Sequence
¶
A sequence is an ordered list of elements, think of it like an array such as [1, 'a', new stdClass]
or a list<T>
in the Psalm nomenclature.
Methods with the symbol indicates that they will trigger loading the generator for deferred and lazy sequences.
Named constructors¶
::of()
¶
The of
static method allows you to create a new sequence with all the elements passed as arguments.
::defer()
¶
This named constructor is for advanced use cases where you want the data of your sequence to be loaded upon use only and not initialisation.
An example for such a use case is a sequence of log lines coming from a file:
The method ask a generator that will provide the elements. Once the elements are loaded they are kept in memory so you can run multiple operations on it without loading the file twice.
Beware of the case where the source you read the elements is not altered before the first use of the sequence.
::lazy()
¶
This is similar to ::defer()
with the exception that the elements are not kept in memory but reloaded upon each use.
Since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail).
::lazyStartingWith()
¶
Same as ::lazy()
except you don't need to manually build the generator.
This is useful when you know the first items of the sequence and you'll append
another lazy sequence at the end.
::mixed()
¶
This is a shortcut for ::of(mixed ...$mixed)
.
::ints()
¶
This is a shortcut for ::of(int ...$ints)
.
::floats()
¶
This is a shortcut for ::of(float ...$floats)
.
::strings()
¶
This is a shortcut for ::of(string ...$strings)
.
::objects()
¶
This is a shortcut for ::of(object ...$objects)
.
Add values¶
->__invoke()
¶
Augment the sequence with a new element.
->add()
¶
This is an alias for ->__invoke()
.
->append()
¶
Add all elements of a sequence at the end of another.
$sequence = Sequence::ints(1, 2)->append(Sequence::ints(3, 4));
$sequence->equals(Sequence::ints(1, 2, 3, 4)); // true
->prepend()
¶
This is similar to ->append()
except the order is switched.
The main advantage of this method is when using lazy sequences. If you want to add elements at the beginning of a sequence but the rest may be lazy then you need to create a lazy sequence with your values and then append the other lazy sequence; but this reveals the underlying lazyness of the call and you need to be aware that it could be lazy.
Instead by using this method you no longer have to be aware that the other sequence is lazy or not.
Access values¶
->size()
¶
This returns the number of elements in the sequence.
->count()
¶
This is an alias for ->size()
, but you can also use the PHP function \count
if you prefer.
->get()
¶
This method will return a Maybe
object containing the element at the given index in the sequence. If the index doesn't exist it will an empty Maybe
object.
$sequence = Sequence::ints(1, 4, 6);
$sequence->get(1); // Maybe::just(4)
$sequence->get(3); // Maybe::nothing()
->first()
¶
This is an alias for ->get(0)
.
->last()
¶
This is an alias for ->get(->size() - 1)
.
->contains()
¶
Check if the element is present in the sequence.
$sequence = Sequence::ints(1, 42, 3);
$sequence->contains(2); // false
$sequence->contains(42); // true
$sequence->contains('42'); // false but psalm will raise an error
->indexOf()
¶
This will return a Maybe
object containing the index number at which the first occurence of the element was found.
$sequence = Sequence::ints(1, 2, 3, 2);
$sequence->indexOf(2); // Maybe::just(1)
$sequence->indexOf(4); // Maybe::nothing()
->find()
¶
Returns a Maybe
object containing the first element that matches the predicate.
$sequence = Sequence::ints(2, 4, 6, 8, 9, 10, 11);
$firstOdd = $sequence->find(fn($i) => $i % 2 === 1);
$firstOdd; // Maybe::just(9)
$sequence->find(static fn() => false); // Maybe::nothing()
->matches()
¶
Check if all the elements of the sequence matches the given predicate.
$isOdd = fn($i) => $i % 2 === 1;
Sequence::ints(1, 3, 5, 7)->matches($isOdd); // true
Sequence::ints(1, 3, 4, 5, 7)->matches($isOdd); // false
->any()
¶
Check if at least one element of the sequence matches the given predicate.
$isOdd = fn($i) => $i % 2 === 1;
Sequence::ints(1, 3, 5, 7)->any($isOdd); // true
Sequence::ints(1, 3, 4, 5, 7)->any($isOdd); // true
Sequence::ints(2, 4, 6, 8)->any($isOdd); // false
->empty()
¶
Tells whether there is at least one element or not.
Transform values¶
->map()
¶
Create a new sequence with the exact same number of elements but modified by the given function.
$ints = Sequence::ints(1, 2, 3);
$squares = $ints->map(fn($i) => $i**2);
$squares->equals(Sequence::ints(1, 4, 9)); // true
->flatMap()
¶
This is similar to ->map()
except that instead of returning a new value it returns a new sequence for each value, and each new sequence is appended together.
$ints = Sequence::ints(1, 2, 3);
$squares = $ints->flatMap(fn($i) => Sequence::of($i, $i**2));
$squares->equals(Sequence::ints(1, 1, 2, 4, 3, 9)); // true
->zip()
¶
This method allows to merge 2 sequences into a new one by combining the values of the 2 into pairs.
$firnames = Sequence::of('John', 'Luke', 'James');
$lastnames = Sequence::of('Doe', 'Skywalker', 'Kirk');
$pairs = $firnames
->zip($lastnames)
->toList();
$pairs; // [['John', 'Doe'], ['Luke', 'Skywalker'], ['James', 'Kirk']]
->aggregate()
¶
This methods allows to rearrange the elements of the Sequence. This is especially useful for parsers.
An example would be to rearrange a list of chunks from a file into lines:
// let's pretend this comes from a stream
$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"];
$lines = Sequence::of(...$chunks)
->map(Str::of(...))
->aggregate(static fn($a, $b) => $a->append($b->toString())->split("\n"))
->flatMap(static fn($chunk) => $chunk->split("\n"))
->map(static fn($line) => $line->toString())
->toList();
$lines; // ['foo', 'bar', 'baz', '']
The flatMap
is here in case there is only one chunk in the sequence, in which case the aggregate
is not called
->chunk()
¶
This is a shortcut over aggregate
. The same example can be shortened:
// let's pretend this comes from a stream
$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"];
$lines = Sequence::of(...$chunks)
->map(Str::of(...))
->map(
static fn($chunk) => $chunk
->toEncoding(Str\Encoding::ascii)
->split(),
)
->chunk(4)
->map(static fn($chars) => $chars->dropEnd(1)) // to remove "\n"
->map(Str::of('')->join(...))
->map(static fn($line) => $line->toString())
->toList();
$lines; // ['foo', 'bar', 'baz', '']
This better accomodates to the case where the initial Sequence
only contains a single value.
->indices()
¶
Create a new sequence of integers representing the indices of the original sequence.
$sequence = Sequence::ints(1, 2, 3);
$sequence->indices()->equals(Sequence::ints(...\range(0, $sequence->size() - 1)));
Filter values¶
->filter()
¶
Removes elements from the sequence that don't match the given predicate.
$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0);
$sequence->equals(Sequence::ints(2, 4));
->keep()
¶
This is similar to ->filter()
with the advantage of psalm understanding the type in the new Sequence
.
use Innmind\Immutable\Predicate\Instance;
$sequence = Sequence::of(null, new \stdClass, 'foo')->keep(
Instance::of('stdClass'),
);
$sequence; // Sequence<stdClass>
->exclude()
¶
Removes elements from the sequence that match the given predicate.
$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0);
$sequence->equals(Sequence::ints(1, 3));
->take()
¶
Create a new sequence with only the given number of elements from the start of the sequence.
->takeEnd()
¶
Similar to ->take()
but it starts from the end of the sequence
->takeWhile()
¶
This keeps all the elements from the start of the sequence while the condition returns true
.
$values = Sequence::of(1, 2, 3, 0, 4, 5, 6, 0)
->takeWhile(static fn($i) => $i === 0)
->toList();
$values === [1, 2, 3];
->drop()
¶
This removes the number of elements from the end of the sequence.
$sequence = Sequence::ints(5, 4, 3, 2, 1)->drop(2);
$sequence->equals(Sequence::ints(3, 2, 1)); // true
->dropEnd()
¶
This removes the number of elements from the end of the sequence.
$sequence = Sequence::ints(1, 2, 3, 4, 5)->drop(2);
$sequence->equals(Sequence::ints(1, 2, 3)); // true
->dropWhile()
¶
This removes all the elements from the start of the sequence while the condition returns true
.
$values = Sequence::of(0, 0, 0, 1, 2, 3, 0)
->dropWhile(static fn($i) => $i === 0)
->toList();
$values === [1, 2, 3, 0];
->slice()
¶
Return a new sequence with only the elements that were between the given indices. (The upper bound is not included)
$sequence = Sequence::ints(4, 3, 2, 1);
$sequence->slice(1, 4)->equals(Sequence::ints(3, 2)); // true
->diff()
¶
This method will return a new sequence containing the elements that are not present in the other sequence.
$sequence = Sequence::ints(1, 4, 6)->diff(Sequence::ints(1, 3, 6));
$sequence->equals(Sequence::ints(4)); // true
->intersect()
¶
Create a new sequence with the elements that are also in the other sequence.
$sequence = Sequence::ints(1, 2, 3)->intersect(Sequence::ints(2, 3, 4));
$sequence->equals(Sequence::ints(2, 3)); // true
->distinct()
¶
This removes any duplicates in the sequence.
$sequence = Sequence::ints(1, 2, 1, 3)->distinct();
$sequence->equals(Sequence::ints(1, 2, 3)); // true
Extract values¶
->toList()
¶
It returns a new array
containing all the elements of the sequence.
->match()
¶
This is a similar approach to pattern matching allowing you to decompose a sequence by accessing the first element and the rest of the sequence.
function sum(Sequence $ints): int
{
return $ints->match(
fn(int $head, Sequence $tail) => $head + sum($tail),
fn() => 0,
);
}
$result = sum(Sequence::of(1, 2, 3, 4));
$result; // 10
For lazy sequences bear in mind that the values will be kept in memory while the first call to ->match
didn't return.
->fold()
¶
This is similar to the reduce
method but only takes a Monoid
as an argument.
use Innmind\Immutable\Monoid\Concat;
$lines = Sequence::of("foo\n", "bar\n", 'baz')
->map(fn($line) => Str::of($line))
->fold(new Concat);
$lines->equals("foo\nbar\nbaz"); // true
->reduce()
¶
Iteratively compute a value for all the elements in the sequence.
$sequence = Sequence::ints(1, 2, 3, 4);
$sum = $sequence->reduce(0, fn($sum, $int) => $sum + $int);
$sum; // 10
Misc.¶
->equals()
¶
Check if two sequences are identical.
Sequence::ints(1, 2)->equals(Sequence::ints(1, 2)); // true
Sequence::ints()->equals(Sequence::strings()); // false but psalm will raise an error
->foreach()
¶
Use this method to call a function for each element of the sequence. Since this structure is immutable it returns a SideEffect
object, as its name suggest it is the only place acceptable to create side effects.
$sideEffect = Sequence::strings('hello', 'world')->foreach(
function(string $string): void {
echo $string.' ';
},
);
In itself the SideEffect
object has no use except to avoid psalm complaining that the foreach
method is not used.
->groupBy()
¶
This will create multiples sequences with elements regrouped under the same key computed by the given function.
$urls = Sequence::strings(
'http://example.com',
'http://example.com/foo',
'https://example.com',
'ftp://example.com',
);
/** @var Innmind\Immutable\Map<string, Sequence<string>> */
$map = $urls->groupBy(fn(string $url): string => \parse_url($url)['scheme']);
$map
->get('http')
->match(
static fn($group) => $group,
static fn() => Sequence::strings(),
)
->equals(Sequence::strings(
'http://example.com',
'http://example.com/foo',
)); // true
$map
->get('https')
->match(
static fn($group) => $group,
static fn() => Sequence::strings(),
)
->equals(Sequence::strings('https://example.com')); // true
$map
->get('ftp')
->match(
static fn($group) => $group,
static fn() => Sequence::strings(),
)
->equals(Sequence::strings('ftp://example.com')); // true
->pad()
¶
Add the same element to a new sequence in order that its size is at least the given one.
$sequence = Sequence::ints(1, 2, 3);
$sequence->pad(2, 0)->equals(Sequence::ints(1, 2, 3)); // true
$sequence->pad(5, 0)->equals(Sequence::ints(1, 2, 3, 0, 0)); // true
->partition()
¶
This method is similar to ->groupBy()
method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with ->groupBy()
it will depend on the original sequence.
$sequence = Sequence::ints(1, 2, 3);
/** @var Map<bool, Sequence<int>> */
$map = $sequence->partition(fn($int) => $int % 2 === 0);
$map
->get(true)
->match(
static fn($partition) => $partition,
static fn() => Sequence::ints(),
)
->equals(Sequence::ints(2)); // true
$map
->get(false)
->match(
static fn($partition) => $partition,
static fn() => Sequence::ints(),
)
->equals(Sequence::ints(1, 3)); // true
->sort()
¶
Reorder the elements within the sequence.
$sequence = Sequence::ints(4, 2, 3, 1);
$sequence = $sequence->sort(fn($a, $b) => $a <=> $b);
$sequence->equals(Sequence::ints(1, 2, 3, 4));
->clear()
¶
Create an empty new sequence of the same type. (To avoid to redeclare the types manually in a docblock)
->reverse()
¶
Create a new sequence where the last element become the first one and so on.
->toSet()
¶
It's like ->distinct()
except it returns a Set
instead of a Sequence
.
->toIdentity()
¶
This method wraps the sequence in an Identity
monad.
Let's say you have a sequence of strings representing the parts of a file and you want to build a file object:
Note
Here Content
and File
are imaginary classes, but you can find equivalent classes in innmind/filesystem
.
Tip
The Identity
returned carries the lazyness of the sequence. This allows composing sequences without having to be aware if the source is lazy or not.
In both cases thanks to the Identity
you only need to specify once if the whole thing is lazy or not.
->safeguard()
¶
This method allows you to make sure all values conforms to an assertion before continuing using the sequence.
$uniqueFiles = Sequence::of('a', 'b', 'c', 'a')
->safeguard(
Set::strings()
static fn(Set $names, string $name) => match ($names->contains($name)) {
true => throw new \LogicException("$name is already used"),
false => $names->add($name),
},
);
This example will throw because there is the value a
twice.
This method is especially useful for deferred or lazy sequences because it allows to make sure all values conforms after this call whithout unwrapping the whole sequence first. The downside of this lazy evaluation is that some operations may start before reaching a non conforming value (example below).
Sequence::lazyStartingWith('a', 'b', 'c', 'a')
->safeguard(
Set::strings()
static fn(Set $names, string $name) => match ($names->contains($name)) {
true => throw new \LogicException("$name is already used"),
false => $names->add($name),
},
)
->foreach(static fn($name) => print($name));
This example will print a
, b
and c
before throwing an exception because of the second a
. Use this method carefully.
->memoize()
¶
This method will load all the values in memory. This is useful only for a deferred or lazy Sequence
, the other sequence will be unaffected.