PHP の Fibers で何か面白いことができないかという試みです。
function subscribe(callable $onNext): void
{
for ($n = 0; $n < 10; ++$n) {
$onNext($n);
}
}
foreach (fromObservable(subscribe(...)) as $n) {
echo $n, PHP_EOL;
}
上記コードのようにコールバックで値が供給される構造を Fiber
で Traversable
に変換することで、foreach
でループすることができます。
Traversable
自体が汎用的なパーツなので、map
のような汎用ユーティリティがある前提下でさまざまな応用が考えられます。
/**
* @template TKey
* @template TSource
* @template TResult
* @param Traversable<TKey,TSource> $source
* @param callable(TSource,TKey):TResult
* @return Traversable<TKey,TResult>
*/
function map(Traversable $source, callable $selector): Traversable;
map(fromObservable(subscribe(...)), fn($n) => $n * $n);
https://www.tehplayground.com/Idh8iJE8SAsx0p5j
<?php
/**
* @template TSource
* @param callable(callable(TSource):void):void $observable
* @return Traversable<TSource>
*/
function fromObservable(callable $observable): Traversable
{
$fiber = new Fiber(static function () use ($observable) {
$observable(static function ($element) {
Fiber::suspend($element);
});
});
return new class ($fiber) implements Iterator {
private int $_key = 0;
/** @var ?TSource */
private mixed $_current;
public function __construct(private readonly Fiber $fiber)
{
}
public function current(): mixed
{
return $this->_current;
}
public function key(): mixed
{
return $this->_key;
}
public function next(): void
{
$this->_current = $this->fiber->resume();
++$this->_key;
}
public function rewind(): void
{
$this->_current = $this->fiber->start();
}
public function valid(): bool
{
return $this->_current !== null;
}
};
}
/**
* @param callable(int):void $onNext
*/
function subscribe(callable $onNext): void
{
for ($n = 0; $n < 10; ++$n) {
$onNext($n);
}
}
foreach (fromObservable(subscribe(...)) as $n) {
echo $n, PHP_EOL;
}