collection icon indicating copy to clipboard operation
collection copied to clipboard

fix(set): map uniqueness

Open abenerd opened this issue 2 years ago • 7 comments

Description

When mapping over a set it is possible to introduce duplicate values which is undesirable.

$set = new \Ramsey\Collection\Set('integer', [1, 2, 3]);
$set->map(fn () => 42);

Motivation and context

This change is so that the behavior of the implementation is respected such as in this case the duplicate values in a Set.

How has this been tested?

Tests have been added for the Set and Collection implementations that a instance of themselves is returned and that when mapping over an existing Set not duplicate values can be introduced.

Types of changes

  • [ ] Bug fix (non-breaking change which fixes an issue)
  • [ ] New feature (non-breaking change which adds functionality)
  • [X] Breaking change (fix or feature that would cause existing functionality to change) The actual return types as declared have not changed but in the case of the Set a new Set is now returned instead of a Collection as well as the new mapping behavior for the Set that does now not allow duplicate values anymore.

PR checklist

  • [ ] My change requires a change to the documentation.
  • [ ] I have updated the documentation accordingly.
  • [X] I have read the CONTRIBUTING.md document.
  • [X] I have added tests to cover my changes.

abenerd avatar Jan 01 '23 09:01 abenerd

I'm not sure map was meant to preserve the class implementation. It just maps all the values to a generic collection (you can also see the value type is mixed instead of using the current type). It will likely break things for a few projects.

SimoTod avatar Jan 01 '23 10:01 SimoTod

I see the BC concern but I also feel like when I'm manipulating a set I want it to behave like a set. Maybe this can be merged into a v3 branch but I think this is an important change.

abenerd avatar Jan 01 '23 11:01 abenerd

Laravel's collections have a mapInto function accepting the collection class name as a second parameter. If the maintainer is open to something like that maybe it could be a good compromise for your use case.

SimoTod avatar Jan 01 '23 18:01 SimoTod

I think the best approach would be to override the map() method on the Set class, rather than introducing the protected $collection property.

That said, map() returns a CollectionInterface. It's not intended to make guarantees about the data it returns. The library is designed to be generic and flexible, allowing you to extend it for your application's needs. I think you could extend it with something like this to achieve your goals:

/**
 * @extends \Ramsey\Collection\AbstractSet<Foo>
 */
class MySet extends \Ramsey\Collection\AbstractSet
{
    public function getType(): string
    {
        return MyType::class;
    }

    /**
     * @param callable(MyType): TCallbackReturn $callback A callable to apply to each
     *     item of the collection.
     *
     * @return MySet
     *
     * @template TCallbackReturn as MyType
     */
    public function map(callable $callback): \Ramsey\Collection\CollectionInterface
    {
        return new self(array_map($callback, $this->data));
    }
}

ramsey avatar Jan 01 '23 21:01 ramsey

I see your point about not introducing a $collection property but I found the map implementations too similar to override them, alternatively one could in the AbstractCollection create a new static collection this way you'd automatically get the correct implementation, although we would then have to take care with the constructor arguments.

I don't think this change would take away any flexibility of the library we would simply ensure the correct behavior for any implementation of a collection and it still allows users to extend any collection they want to add their own behavior.

abenerd avatar Jan 02 '23 09:01 abenerd

we would simply ensure the correct behavior

Is the correct behavior for the map function to always return the same type of collection?

ramsey avatar Jan 02 '23 18:01 ramsey

I would argue so yes, when I create a new Set I expect it to behave a certain way and until I explicitly say make this Set into a Collection, or Whatever I want to be sure in the assumption that I am working with a Set.

abenerd avatar Jan 03 '23 06:01 abenerd