HH\Lib\C\sum and HH\Lib\C\sum_float accept Traversable<mixed>
jesseschalken opened this issue · 1 comments
jesseschalken commented
HH\Lib\C\sum
and HH\Lib\C\sum_float
both accept Traversable<mixed>
and so are likely to lead to errors in the way they are used.
The reason they accept Traversable<mixed>
is because they optionally accept a function to extract the numbers to be summed from the elements of the Traversable
. In my opinion this should instead be done by mapping over the Traversable
first and doing a sum on the result.
To avoid allocating an intermediate array/vec, a lazy_map
function can be used, like this:
function lazy_map<Tin, Tout>(Traversable<Tin> $t, (function(Tin): Tout) $f): Traversable<Tout> {
foreach ($t as $v) {
yield $f($v);
}
}
Or, if the result of lazy_map
should be reusable:
class ReusableGenerator<Tk, Tv> implements \IteratorAggregate<Tv>, KeyedTraversable<Tk, Tv> {
public function __construct(private (function(): \Generator<Tk, Tv, void>) $generator) {}
public function getIterator(): \KeyedIterator<Tk, Tv> { return new ReusableGeneratorIterator($this->generator); }
}
class ReusableGeneratorIterator<Tk, Tv> implements KeyedIterator<Tk, Tv> {
private \Generator<Tk, Tv, void> $iter;
private bool $first = true;
public function __construct(private (function(): \Generator<Tk, Tv, void>) $gen) {
$this->iter = $gen();
}
public function __clone(): void {
$this->iter = clone $this->iter;
}
public function rewind(): void {
if (!$this->first) {
$gen = $this->gen;
$this->iter = $gen();
$this->first = true;
}
}
public function next(): void {
$this->iter->next();
$this->first = false;
}
public function valid(): bool { return $this->iter->valid(); }
public function current(): Tv { return $this->iter->current(); }
public function key(): Tk { return $this->iter->key(); }
}
function lazy_map<Tin, Tout>(Traversable<Tin> $t, (function(Tin): Tout) $f): Traversable<Tout> {
return new ReusableGenerator(() => {
foreach ($t as $v) {
yield $f($v);
}
});
}
Then this
use function HH\Lib\C\sum;
function sum_bars(vec<Foo> $foos): int {
return sum($foos, $foo ==> $foo->getBar());
}
becomes
use function HH\Lib\C\{lazy_map, sum};
function sum_bars(vec<Foo> $foos): int {
return lazy_map($foos, $foo ==> $foo->getBar()) |> sum($$);
}
fredemmott commented
Math\sum_float: take num, cast to float
Math\sum: hopefully take ints, maybe num + cast to int