hhvm/hsl

HH\Lib\C\sum and HH\Lib\C\sum_float accept Traversable<mixed>

jesseschalken opened this issue · 1 comments

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($$);
}

Math\sum_float: take num, cast to float
Math\sum: hopefully take ints, maybe num + cast to int