azjezz / psl

📚 PHP Standard Library - a modern, consistent, centralized, well-typed, non-blocking set of APIs for PHP programmers
MIT License
1.2k stars 69 forks source link

[DataStructure] iterating over data structures #83

Open azjezz opened 3 years ago

azjezz commented 3 years ago

see discussion in #53 ( https://github.com/azjezz/psl/pull/53#discussion_r500772765 )

Questions:

azjezz commented 3 years ago

/cc @veewee

veewee commented 3 years ago

Would it make sense to make the data structures immutable?

That way the questions are answered with:

Not sure if it makes sense from a consumer pov though.

I've also researched some other languages: https://rosettacode.org/wiki/Priority_queue

Most use something like while(!empty)... or while (0 !== count) to iterate instead of making it an iterator. Some languages support both iterating and the while approach. I think it would be nice if you could use the functions inside the Iter\ namespace on the data structures.

azjezz commented 3 years ago

I like the idea of immutable data structures.

If i understand correctly:

$queue = new Queue();
$queue = $queue->enqueue('hello');
$other = $queue->enqueue('hey');

$queue->count(); // 1 ['hello']
$other->count(); // 2 ['hello', 'hey']

$queue->dequeue(); // 'hello'

$queue->count(); // 0 []
$other->count(); // 2 ['hello', 'hey']

maybe we can do the same as in Collection, and have Queue, PriorityQueue, Stack be immutable, with another variant MutableQueue, MutablePriorityQueue, and MutableStack that are mutable ( see Psl\Collection\Vector vs Psl\Collection\MutableVector) ?

veewee commented 3 years ago

The dequeue method in your example should also be immutable then? Meaning the while will need to reassign the parameter pointing to the new version of the queue as well (which might be a bit counterintuitive).

Having both options is nice, but also more work to create and maintain. They should behave in a similar way, meaning that the 3 questions on top need to be answered in the same way for both implementations.

If I understand the haskell priority queue well, it also works in an immutable way. (But I am an absolute noob in that language :) )

azjezz commented 3 years ago

Here's a real world usage of PriorityQueue ( https://github.com/nuxed/http-server/blob/develop/src/Nuxed/Http/Server/Handler/NextMiddlewareHandler.hack#L5-L26 ):

final class NextMiddlewareHandler implements Server\IHandler {
  /** @var SplPriorityQueue<Server\IMiddleware> $queue */
  private SplPriorityQueue $queue;
  private Server\IHandler $handler;

  public function __construct(SplPriorityQueue $queue, Server\IHandler $handler) {
    $this->queue = clone $queue;
    $this->handler = $handler;
  }

  public function handle(Message\IServerRequest $request): Message\IResponse 
  {
    if (0 === $this->queue->count()) {
      return await $this->handler->handle($request);
    }

    $middleware = $this->queue->dequeue();

    return $middleware->process($request, $this);
  }
}

if we switch to immutable DS, this is how the API would look like ( correct me if i'm wrong ):

final class NextMiddlewareHandler implements Server\IHandler {
  /** @var SplPriorityQueue<Server\IMiddleware> $queue */
  private SplPriorityQueue $queue;
  private Server\IHandler $handler;

  public function __construct(SplPriorityQueue $queue, Server\IHandler $handler) {
    $this->queue = clone $queue;
    $this->handler = $handler;
  }

  public function handle(Message\IServerRequest $request): Message\IResponse 
  {
    if (0 === $this->queue->count()) {
      return await $this->handler->handle($request);
    }

    [$queue, $middleware] = $this->queue->dequeue();
    $this->queue = $queue;

    return $middleware->process($request, $this);
  }
}

the return type of dequeue is now bothering me, i'm not sure if immutable would be a good idea here since most PHP software seems to be using it in a mutable context.

veewee commented 3 years ago

I think tupples are getting more and more known in the community. The immutable implementation is indeed more verbose. Therefore, I suggest following things:

Next on the topic of iterations:

examples:

// Iterables
$items = map([...$queue], ($value) => 'something');
$items = filter([...$queue], ($event) => $event instanceof SomeEvent);
// ...

// Looping over them with more control:
// We could add an isEmpty - which makes sense
while ($queue->count()) {
    $value = $queue->dequeue();
}

//  More advanced -> see your NextMiddlewareHandler

For your specific use-case a mutable version might make more sense. Even though I don't fully grasp the idea behind the NextMiddleware. It does something different each time it is handled, but in a regular server middleware stack - it only goes through the handle once?

For immutable it could look like this:

public function dispatch($event)
{
    $queue = $this->queue;

    while ($queue->count()) {
        [$queue, $subscriber] = $queue->dequeue();
        $subscriber($event);
    }

    return $event;
}

Which could also be used with iterator functions to make it less verbose.

rauanmayemir commented 3 years ago

It would be really nice to have a fallback with an optional mapping to https://github.com/php-ds.