PHPGenerics / php-generics-rfc

Mirror of https://wiki.php.net/rfc/generics for easier collaboration
186 stars 1 forks source link

I do not understand the need behind it. #32

Closed jens1o closed 6 years ago

jens1o commented 6 years ago
class Entry<KeyType, ValueType>
{
    protected $key;
    protected $value;

    public function __construct(KeyType $key, ValueType $value)
    {
        $this->key   = $key;
        $this->value = $value;
    }

    public function getKey(): KeyType
    {
        return $this->key;
    }

    public function getValue(): ValueType
    {
        return $this->value;
    }
}

The definition in line 1 is completely useless, isn't it? There already is typechecking going on in the constructor? How does this help me?

natebrunette commented 6 years ago

You can reuse Entry with different types.

new Entry('key', new Foo()); // 1
new Entry(0, new Bar()); // 2

1 will be an Entry<string, Foo> while 2 is an Entry<int, Bar>

jens1o commented 6 years ago

Why I do not use abstract classes that can handle such types?

natebrunette commented 6 years ago

What classes would you create?

jens1o commented 6 years ago

A class (like IntEntry) that inherits from something like StringEntry that is only responsible to compensate that I only have an Int and need to fetch the data so it works as expected.

natebrunette commented 6 years ago

I question your data model if IntEntry inherits from StringEntry, but let's assume it does here. What about typing the values? Do you create two new classes for each type (int, double, string, bool, array, and every class that might want to use it)? What if a library is providing this class and doesn't know what types will be available? What if key can accept complex types? Are you creating n x m different classes to handle every possibility?

jens1o commented 6 years ago

When I would not create these amount of classes, I'd need to make one huge class that accept a mixed type and then needs to check every little thing to extract somehow the data I need?

It makes more sense for me to create a class for every thing that exists to keep my code clean.

natebrunette commented 6 years ago

No, using generic programming, when you do

$entry = new Entry('key', new Foo());

You're specifying that the key is a string and the value is a Foo (through inference). Then when you call

$foo = $entry->getValue();

You know that $foo is a Foo.

You could also explicitly set the types, but it's not necessary.

$entry = new Entry<string, Foo>('key', new Foo());
jens1o commented 6 years ago

But isn't it easier to just have dedicated classes? Otherwise, when I call getValue I need to handle each possible return value at each position I call this method. When I just create a class that is dedicated to provide me a seamless interface, I will have a lot less code to maintain, and this idea is completely useless.

I do not see the point why I should do this.

natebrunette commented 6 years ago

No, as I've stated previously, you know exactly what types are available. Let's use a better example:

class Collection<T>
{
    private $items = [];
    public function add(T $item)
    {
        $this->items[] = $item;
    }
    public function get(int $index): T
    {
        return $this->items[$index];
    }
}

Without generics, you could not use this class in a type safe way without creating different subclasses, as you mentioned. Although, because you have to default to any type, you'd be violating LSP if you subclassed this class and added type information.

With generics, you can have your collection hold different types of items reusing the same class.

new Collection<int>();
new Collection<string>();
new Collection<Hat>();

When using the collection, you know exactly what type of collection you're using because you type to it.

If you have a function that operates on the collection, you do not just type to Collection, you type to the type of collection you need.

function addAHat(Collection<Hat> $hatCollection, Hat $hat): void
{
    $hatCollection->add($hat);
}

Because you're typing to Collection<Hat>, the only thing that's allowed in the collection are Hats. The following would fail.

function addAHat(Collection<Hat> $hatCollection, Hat $hat): void
{
    $hatCollection->add(1);
}

The following would also fail

function addAHat(Collection<Hat> $hatCollection, Hat $hat): void
{
    $hatCollection->add($hat);
}
addAHat(new Collection<int>(), new Hat());

You end of writing a lot less code this way because you can reuse a single class in a type safe way.

jens1o commented 6 years ago

Oh. Now I got it. :D

I'm so sorry for my misunderstandings :(