Как стать автором
Обновить

О дженериках в PHP и о том, зачем они нам нужны

Время на прочтение 6 мин
Количество просмотров 32K
Всего голосов 53: ↑44 и ↓9 +35
Комментарии 89

Комментарии 89

Впервые узнал про дженерики из Java. Абсолютно согласен с тем, что это была бы крайне полезная фича. Особенно для различных библиотек.

P.S. Я читаю теги!
Дженерики это фигня по сравнению с шаблонами C++.

Никто не спорит, с другой стороны, идея языка внутри языка не столь хороша. Никто не спорит о пользе которые дают шаблоны, вопрос в реализации.

Если циклически пройти по нашему набору постов, то в результате получим критическую ошибку.

PHP Fatal error: Uncaught Error: Call to a member function getId() on string
Так мы работаем с массивом постов, или с массивом рандомных элементов?

Согласен, статья рассматривает какую-то гипотетическую проблему, а не практическую. Если контролировать формирование массива, то описанная ситуация невозможна.

Но это означает что нам нужно делать специализированные коллекции на каждый случай. А так можно было бы делать что-то типа:


interface ArticlesProvider
{
    public function getArticles(): Collection<Article>;
}

Еще если добавить к этому элиасы типов как в Hack то вообще удобно:


type ArticlesCollection = Collection<Article>;

Количество кода резко сокращается, но при этом сохраняются все плюсы специализированных коллекций.

Чот странное ощущение от этих дженнриков. С одной стороны ругают php за динамическую типизацию, с другой стороны тащат в пых динамическое все, следить за которым еще то удовольствие.

Полочается «Возьми то, не знаю что, и верни его точно существующее свойство или метод.

Сорян, хрень какая то, в масштабах 10 — 20 сущностей.
Если больше то может и имеет смысл
с другой стороны тащат в пых динамическое все, следить за которым еще то удовольствие.

Давайте разберемся с терминологией. "Статическое" — это когда информация о чем-то доступна из кода. "Динамическое" — то что происходит в рантайме.


Почему статическое хорошо — потому что можно не выполняя код проверить корректность определенных вещей. К примеру возможность декларировать типы и указывать их в сигнатурах методов позволяет нам при помощи статического анализа сильно уменьшить количество потенциальных багов.


Почему динамическое хорошо — потому что когда все статическое, чтобы что-то поменять нужно менять код, что не слишком удобно.


К примеру у нас есть объект представляющий некую посылку. Эдакий контейнер. Нам без разницы что там, нам нужно просто дать ему поведение "хранения" какой-то вещи, возможность задать адресата и узнать пустая посылка или нет. Если мы будем жестко (статически) декларировать тип хранимого объекта, нам придется множить количество реализаций посылок на каждый возможный тип объектов которые мы хотим переслать. А еще — мы хотим сделать это дело библиотекой которую можно реюзать между проектами. Или у нас вайтлейблы и для каждого могут быть свои типы объектов. И на каждый чих надо множить подтипы.


С другой стороны код, который будет принимать нашу посылку, может быть завязан на том что в ней. И мы хотим выразить эту зависимость статически, дабы иметь возможность дешево проверить на соответствие требованиям.


По сути при помощи дженериков вы можем обеспечить себя механизмом посылок с late binding, которые будут существовать в единственном экземпляре, но при этом клиентский код будет иметь возможность статически декларировать зависимость от типа посылок.


Теперь немного про "PHP ругают за динамическую..." Ругают его в основном за то что там типизация не только динамическая но еще и слабая. Скажем python со своей сильной динамической типизацией допускает меньший класс ошибок. В Java скажем система типов тоже не сахар, хоть и статическая.

А ведь вы меня убедили, пора пересматривать мои старческие подходы :-) Спасибо!
А что не так с системой типов в Java?
Дженерики как раз и привносят статическую типизацию.
Реализовать дженерики в PHP можно проще простого, достаточно чу-чуть изменить конструктор и метод offsetSet:
class Collection implements Iterator, ArrayAccess {
//    ...

    /** @var string */
    private $className = null;

    /**
     * @param string $T
     */
    public function __construct($T) {
        if (is_string($T) && class_exists($T)) {
            $this->className = $T;
        } else {
            throw new \InvalidArgumentException("T must be name of class.");
        }
        $this->position = 0;
    }

//...
    public function offsetSet($offset, $value) {
        if (!($value instanceof $this->className)) {
            throw new \InvalidArgumentException("value must be instance of {$this->className}.");
        }
        if (is_null($offset)) {
            $this->array[] = $value;
        } else {
            $this->array[$offset] = $value;
        }
    }
}


$postCollection = new Collection(Post::class);

$postCollection[] = new Post(); //ok
$postCollection[] = 'Post'; //throw Exception


Единственное чего не хватает, так это дополнения в IDE. Но и как выше говорили, достаточно нормально следить за заполнением массива.

UPD: Если указать тип для $postCollection через PHPDoc как массив постов, то будет подсказывать
/** @var Post[] $postCollection */
$postCollection = new Collection(Post::class);

Идея то в том что бы на уровне клиентского кода декларировать зависимости. Максимум что можно сделать для вашего варианта — плодить подтипы.

Плодить подтипы не нужно. В клиентском коде достаточно указать имя класса в конструкторе класса-коллекции.

$collection = new GenericCollection<Post>(); //Вариант из статьи
$collection = new Collection('Post'); //Мой вариант

Обе строчки «равнозначны».
НЛО прилетело и опубликовало эту надпись здесь
Для различия содержимого достаточно добавить в класс коллекции пару методов:
    /**
     * Замена для instanceOf
     * @param $class
     * @return bool
     */
    public function isInstanceOf($class)
    {
        return is_a($this->className, $class, true);
    }

    /**
     * @return string
     */
    public function getClassName()
    {
        return $this->className;
    }
НЛО прилетело и опубликовало эту надпись здесь
Тогда везде в клиентском коде нужно проверять тип элементов в коллекции. Это лишь немногим удобней обычного массива в пхп


Похоже мы с вами друг-друга не понимаем. Будьте добры, приведите пример, как вы будите использовать джинерики, для отличия
$collection = new Collection<Post>();

от
$collection = new Collection<User>();
НЛО прилетело и опубликовало эту надпись здесь
Пока в ПХП такая типизация, какая она есть, без костылей с аннотациями, к сожалению не обойтись, но кажется я вас понял. Если бы дженерики были нативные, то приведенный ниже код гарантировал бы, что внутри функции useCollection все элементы $posts являются экземплярами класса Post (если это не так, то будет эксешпшен при вызове).
function useCollection(Collection<Post> $posts) {
    // ....
}


Мой код позволяет добиться такого поведения, без особых заморочек:
function useCollection(Collection $posts) {
    if (!$posts->isInstanceOf(Post::class)) {
        throw new InvalidArgumentException();
    }
    /** @var Post[] $posts */
    // ....
}

Это 100% гарантирует, что каждый элемент коллекции является экземпляром Post

Дело вот в чем:
abstract public mixed offsetGet ( mixed $offset )


Никакие решения на базе mixed не позволят избавить клиентский код от необходимости проверок типа элементов коллекции.


Если вы внимательно посмотрите в мою реализацию метода offsetGet, то увидите, что в коллекцию могут быть добавлены только объекты того класса, который был указан при создании коллекции, так что необходимости проверять каждый элемент коллекции перед использованием нет.
НЛО прилетело и опубликовало эту надпись здесь
Единственное преимущество у вас, это то, что не нужно проверять каждый элемент.

Т.е. делает тоже самое, что и указание джинерика в сигнатуре метода.
НЛО прилетело и опубликовало эту надпись здесь
В пыхе достаточно способов напихать в вашу коллекцию что попало

Прошу, приведите пример кода, но без использования рефлексии.
А почему собственно без?

Потому что в общем случае это нарушение инкапсуляции.


И за регулярное использование рефлексии в обычных проектах лично я буду отбивать руки

Рефлексия — это одно из самых мощных средств для реализации метапрограммирования. А учитывая то, что в PHP оно ещё и невероятно быстрое, по сравнению, например, с Java — непонятно почему от неё стоит отказываться.


С другой стороны и помимо рефлексии есть множество способов реализовать задуманное, но думаю проксирование сквозь Stream API, токенизация и построение минимального базового AST чуть более накладно и на некоторый порядок сложнее в осознании.


В любом случае, даже без рефлексии, за которые вы безоснавательно кому-то там отрубаете что-то можно реализовать в PHP, без расширений, на уровне языка, полную копию синтаксиса из RFC с полным аналогом поведения.

Рефлексия — магия, которая ведет к неожиданному поведению, непонятному стек-трейсу и плохому автодополнению

Повторюсь.
В общем случае использование рефлексии в вашем коде это признак проблем архитектуры.


Не такой большой объем задач действительно требует её. И эти задачи не пишутся ежедневно.


Что касается самого поведения, то стартовый вариант коллекции без дженериков меня вполне устраивает.
Он выполняет ровно то, что и требуется

Три причины:
1. Использование рефликсия является самым очевидным способом, обойти интерфейс и запихать в приватное поле все что вздумается, а в исходном комменте заявлено несколько способов.
2. Я точно знаю как «сломать» коллекцию через рефликсию, хочется увидеть что-то новое, чего я не знаю.
3. Я не очень хорошо знаком с возможностями рефлексии в других языках, но у верен, что в C# можно с ее помощью сломать даже местные дженерики.
  1. Я точно знаю как «сломать» коллекцию через рефликсию, хочется увидеть что-то новое, чего я не знаю.

можно создать замыкание в контексте интересующего вас класса (Closure::bind) и таким образом "надломить инкапсуляцию". Но идея не в этом, это как рассматривать только самый негативный кейс.


На самом деле что нас интересует, так это то что код который продьюсит коллекцию и код который ее принимает — это таки разные модули. И что нас на самом деле интересует — это локализация контрактов в пределах модулей. Дженерики как раз таки это позволяют делать безболезненно.

НЛО прилетело и опубликовало эту надпись здесь
От этого можно защитится установив в конструкторе защиту от переопределения.
НЛО прилетело и опубликовало эту надпись здесь
А вот это зачет, возьму себе в копилку.

Вот только статические анализаторы эту запись не поймут. А значит смысла не так много.

К сожалению, из-за особенностей типизации PHP, статистически анализаторы много-где не могут помочь. Например, отсутствие возможности перегрузки методов вынуждает отказываться даже от typehintig-га.

public void method (ClassA a) {
// ...
} 
public void method (ClassB a) {
// ...
} 



в PHP превращается в
public method ($a) {
// работаем без поддержки анализатора =(
} 

Думаю здесь нужен был интерфейс в качестве параметра или метод с другим именем, раз вы передаёте другой класс. Перезагрузки действительно нет и перезагружать такими костылями
public method ($a) {
// работаем без поддержки анализатора =(
}

разные типы не стоит.
Интерфейс это вариант, при условии что оба класса его реализуют. А разделение на методы — это вынос проблемы на уровень выше, вызывающему коду теперь нужно принимать решение о том, какой метод вызвать.

Только если при этом вы не нарушите LSP. Чаще же проще сделать два метода (в некоторых языках, например в Objective-C перегрузка именно так и работает, неявно создаются просто разные методы, а синтаксис позволяет это завуалировать).


Чаще подобные хотелки возникают из желания максимально упростить интерфейс разрабатываемой штуки. Дабы проще было использовать.

отсутствие возможности перегрузки методов

/**
 * @param ClassA | ClassB $a
 */
public function method($a)
{
}

и вуаля.


С другой стороны — вам часто это нужно?

Нет, нужно не часто, рассматривался гипотетический случай. А выше на меня «наехали», сказав что указание типа через нотации — это выкрутасы =)

меня лично интересует больше возможности типы трекать ДО выполнения кода, так что мне такой вариант хоть и не очень нравится но сойдет.

НЛО прилетело и опубликовало эту надпись здесь
приведите пример, как вы будите использовать джинерики, для отличия

public function doSomethingWithUsers(Collection<User> $user)
{
    //     
}
Собственно, выше я писал вариант для этого. Но суть «спора» в том, что я рассуждаю с позиции того, что сейчас есть в языке (собственно, я так и написал в моей первом комменте).

Суть статьи как раз и заключается в том что то, что есть в языке сейчас не покрывает нужды разработчиков.

Для различия содержимого достаточно добавить в класс коллекции пару методов:

тем самым вы перекладываете проверки не только на рантайм но еще и на пользователя ваших коллекций.


В этом нет никакого практического смысла — проще использовать просто массивы.

Может тогда уже сразу переходить на java? Не, ну серьезно, скоро пых станет гораздо мудреннее, чем java
В PHP, как и в Java, гораздо важнее виртуальная машина, нежели синтаксис языка. Поэтому и Hack, и Kotlin, и Scala, а машинки все те же.
Java-да. Даже не сама виртуальная машина, а ее архитектура, которая описывается, насколько я помню в java blueprints(могу ошибаться) и доступна разработчикам сторонних виртуальных машин.

Насчет PHP — не согласен. Во-первых, потому что изначально php имел довольно низкий порог входа и быстрое получение результата, обусловленные в том числе и простотой синтаксиса. Во-вторых, вм php все же проигрывает jvm по производительности. Ну и в третьих, большинство споров, улучшений и прочего в мире php связаны не с расширением функционала стандартной библиотеки, ни с повышением производительности, а именно с добавлением различного синтаксического сахара и применения лучших паттернов проектирования(порой кажется, что мы пытаемся играть в серьезных ынтырпрайз разработчиков со всеми этими фабриками, добавлением дженериков, сложными архитектурами кода и прочим).

Но, нужно отдать должное, что многие нововведения касательно синтаксиса не навязываются разработчиками и не ломают обратную совместимость.
Он и сейчас имеет низкий порог входа.
Даже не сама виртуальная машина, а ее архитектура

Все же нет, как раз таки виртуальная машина. Никто не хочет писать свои виртуальные машины.


изначально php имел довольно низкий порог входа и быстрое получение результата

Низкий порог входа в PHP не из-за того что он ничерта не умеет, а из-за модели выполнения. Пришел запрос, приложенька отработала, респонс ушел, приложенька отчищена из памяти. весь цикл обработки запросов изолирован. Любой школьник может делать страшные вещи, не заботиться о сайд эффектах, и код будет работать.


Можно ли так делать сегодня? Все еще да, так что с порогом входа в PHP ничего не изменилось.


Во-вторых, вм php все же проигрывает jvm по производительности.

Причем тут это? Да, он проигрывает и будет проигрывать так как все же динамическая система типов добавляет некислый оверхэд. И да, даже если у нас появится AOT/JIT, всеравно PHP будет медленнее хотя бы в силу модели выполнения.


Ну и в третьих, большинство споров, улучшений и прочего в мире php связаны не с расширением функционала стандартной библиотеки, ни с повышением производительности, а именно с добавлением различного синтаксического сахара

а теперь посмотрите изменения за последние 5 лет. В основном — изменения в плане производительности, расширение стандартной библиотеки PHP (password api вспомните, сейчас еще libsodium запилили), генераторы, минимум сахара.


(порой кажется, что мы пытаемся играть в серьезных ынтырпрайз разработчиков со всеми этими фабриками, добавлением дженериков, сложными архитектурами кода и прочим).

Попробуйте раскрыть мысль. Ибо звучит это все так как будто вы предлагаете два варианта:


  • не пытаться писать серьезных приложений на PHP
  • писать все строго в процедурном стиле.

Лично мне нужен язык с динамической системой типов, который дает опциональную возможность (то есть если я этого не буду делать, все продолжит работать) явно декларировать все зависимости и используемые типы.

Да? А я-то думаю, почему я пользуюсь пыхом.
Из-за виртуальной машины, вот оно что.
По поводу Итераторов. Бывает полезно применять json_decode/code

Объект = json_decode(json_encode($Дерево));
$Итератор = new \RecursiveIteratorIterator( new \RecursiveArrayIterator($Объект), \RecursiveIteratorIterator::SELF_FIRST );


И Классы для фильтрации внутри Итераторов

class Фильтрация
{
  public function Фильтрация($Трансформер)
  {
    echo "Фильтрация\n";

    $array = array("test1", array("taste2", "test3", "test4"), "test5");
    $iterator = New \RecursiveArrayIterator($array);
    $filter = New StartsWithFilter($iterator, "test3");

    foreach(New \RecursiveIteratorIterator($filter) as $key => $value)
    {
      echo $value . "\n";
    }
  }
}


class StartsWithFilter extends \RecursiveFilterIterator
{
  protected $word;

  public function __construct(\RecursiveIterator $rit, $word)
  {
    $this->word = $word;
    parent::__construct($rit);
  }

  public function accept()
  {
    return $this->hasChildren() OR strpos($this->current(), $this->word) === 0;
  }

  public function getChildren()
  {
    return New self($this->getInnerIterator()->getChildren(), $this->word);
  }
}

Цель статьи — показать полезность такой вещи в PHP. Почему дженериков не было в 7.2 — потому что для того чтобы их добавить без необходимости жертвовать производительностью нужно было чуть поменять некоторые внутренние структуры данных, что уже не так просто. Но скажем к 8-ой версии это вполне возможно.

Проблема в том что generics, висят в Under Discussion с Created 2015/04/28 и как бы стол и ныне там.
Так увидеть их в 8 тоже маловероятно. Для того чтоб куда-то что-то двигалось нужно чтоб прошло голосование (In voting phase). А пока это one-person-opinion-feature.

А пока это one-person-opinion-feature.

RFC обсуждалась достаточно долго, была реализация (и не одна) и все упирались в производительность. Вот тут больше подробностей почему все плохо. Проблема не с идеей а с имплементацией.

Неуверенность в содержимом массива и влиянии разбросанности кода на производительность и удобство сопровождения, а также неудобство написания дополнительных проверок заставили меня долго искать решение получше.


На мой взгляд «Неуверенность в содержимом массива» — основной признак говнокода…
Ага, и лучше что бы компилятор не давал говнокод писать.
есть еще такое https://github.com/ircmaxell/PhpGenerics
PHP Fatal error: Uncaught Error: Call to a member function getId() on string

Кроме говнокодеров такое у кого-то может возникнуть? :)

Допустим, у вас есть метод, которому нужен массив блог-постов:

Так а выше о чем говорилось?.. :)

RFC предназначен для PHP 7.1, о его будущем нет никакой дополнительной информации.

А ниче, что он давно вышел? :)

Но вам в любом случае понадобится создавать классы для каждого доступного типа.

Да, а параметры придумала сотона мохнатая.

class GenericCollection<T> implements Iterator, ArrayAccess
О, нет.
Не превращайте PHP в С++.

Благодаря дженерикам, наш сайт упадет не момент получения неправильного элемента, а в момент записи? :)

Все это повышает сложность языка. Прилив молодой крови может поуменьшиться и язык умрет.
Кроме говнокодеров такое у кого-то может возникнуть? :)

Да. Просто не так красочно как в статье, но схлопотать null в середине коллекции вы запросто можете.


А ниче, что он давно вышел? :)

А ниче что этот абзац именно об этом? В частности фраза "о его (RFC) будущем ничего не известно".


Не превращайте PHP в С++.

Просто не используйте их если вам и так все ок.


Благодаря дженерикам, наш сайт упадет не момент получения неправильного элемента, а в момент записи? :)

Если штука которая пишет в коллекцию обязалась соблюдать контракт что мол "я буду всегда записывать коллекции только с такими-то типами" то да. Тогда это будет равносильно "падай когда нарушил контракт". Это намного лучше чем ловить непонятные сайд эффекты дальше по приложению. Не говоря уже о том что статические анализаторы будут неплохо это трекать.


Все это повышает сложность языка. Прилив молодой крови может поуменьшиться и язык умрет.

  1. это увеличивает возможности языка. Вас никто этим пользоваться не заставляет. PHP как не требовал что бы все типы выводились так и не требует.


  2. языки умирают не из-за "оттока" молодой крови, а из-за отсутствия спроса. Когда язык не развивается — спрос падает.
Вместо создания проверки и выброса исключения, куча кода, которая приводит к той-же проверке и выбросу исключения. А точнее это просто фильтрация данных, которая должна быть где-то до foreach ($posts as $post)… Пример из статьи напоминает:
query("SELECT * FROM posts WHERE id = " . $_GET['id']);
Кто все эти люди, хотящие странного?
Неудачники из других, непопулярных языков?
Дженерики, на самом деле, гараздо мощнее, чем только «массив типов».
Вот к примеру на C# можно написать так:

tank.GetAbility<AttackAbility>().


И дальше, после точки, вы будете уверены в типе переменной, а ваша IDE корректно автодополнит такой код.

Ну или у вас, скажем, с сервера по API часто получается такая структура:

{
  pageCount: 10,
  currentPage: 3,
  items: []
}


И у вас все приходит в таком формате — список новостей постранично, список пользователей постранично, список комментариев постранично. Можно на каждую структуру создавать свой класс, а можно что-то вроде такого:

class ItemsPageOf<TItem> {
  int pageCount;
  int currentPage;
  TItem[] items;
}


А потом просто использовать Дженерики:
ItemsPageOf<MessageItem> messages;
ItemsPageOf<ArticleItem> articles;
ItemsPageOf<CommentItem> comments;


messages.items[0]. // угадайте, какой тут тип?
Очень нужная вещь в языке. Стараемся использовать максимум из языка, type hint первый важный шаг. Теперь нужны генерики и структуры как в С. Поддержание нескольких подходов (например, динамическая/статическая типизация) и парадигм залог использования языка в будущем.
В целом идея отличная, хоть и усложняет язык. Я о дженериках узнал из Java и они мне очень понравились как раз ключе удобства. Но в остальном оно только замедлит PHP.

Но вот пример из поста какой-то надуманный, набыдлокоженный и изначально неправильный. С какого перепуга мы перебираем массив случайных элементов? У меня за несколько лет практики небыло ни одного случая, когда в массив попадали настолько неоднородные элементы: такого небывает даже у совсем-новичков.
Прошу прощения за отвлеченный вопрос…

А разве применение разных типов данных в одной коллекции (элементов перебираемого массива) не является косяком?

В контексте самой коллекции (а именно к ним применяются дженерики) — нет. Коллекции нет дела что она хранит. Дженерики как раз таки позволяют клиентскому коду (коду который собирается что-то там перебирать) жестко декларировать что он хочет перебирать.

Честно говоря, единственный приведенный пример якобы пользы шаблонов — типизированная коллекция — выглядит надуманным.

То есть всё понятно в плане что это за языковая фича. Непонятно — зачем она? Какие еще есть внятные применения?

Имхо это тупик какой-то. Гораздо интереснее было бы форсить RFC про кастомные типы. Тогда бы никакие дженерики для типизированных массивов (коллекций) не потребовались бы.

Полно применений для инфраструктурных вещей. Например, с классом, реализующим клиент HTTP Rest API на дженериках можно будет писать что-то вроде $client = new RestClient('example.com/api/v1/users'); $user = $client->get(123); гарантированно получая инстанс User. Да, есть и другие способы, сделать это, например, передавая имя класса как строковый параметр и используя new или ReflectionClass::newInstance с ним или просто на каждый чих создавая новый класс, но это, как минимум, менее удобно, а, главное, никаких гарантий на уровне языка ни разработчику, ни рантайму, ни статическим анализаторам.

Но главное, по-моему, именно обычные структуры данных: коллекции, карты, списки, стэки, множества и т. п.

RestClient<User>('example.com/api/v1/users')

Я полностью поддерживаю этот RFC и в джаве успешно ими пользовался, но, признаться, не припоминаю не единого случая, что в коллекцию или массив прилетал внезапно какой-то другой элемент, отличный от ожидаемого.


Так что хочется задаться вопросом: Да, это всё удобно и круто, но насколько этот RFC покрывает проблемы, с которыми сталкиваются разработчики при написании своего ПО? От каких ошибок оно избавляет? Кажется, что эти плюшки нужны только для автокомплита и не более. Разве нет?

Смотреть просто надо не с точки коллекций, что мол элемент неожиданный прилетает, а больше в сторону клиентского кода который ожидает коллекцию с определенными элементами.


От каких ошибок оно избавляет?

function onlyPremiumUsers(array<User> $users): array<User>
{
    // ..
}

А если еще и сделать элиасы для типов… ух


type Users = array<User>;
type PremiumUsers = array<User>;
// ...
function onlyPremiumUsers(Users $users): PremiumUsers
{
    // ..
}

выразительность, дешевая проверка простеньких бизнес правил…

Ну "клиентам" это не мешает видеть return докблок:


/**
 * @return iterable|User[]
 */
public function premium(): iterable
{
    // ...
}

А на счёт тайпалиасов — это вообще огонь. Но, кажется, без наличия деклараций, аналогичных инвариантам DbC — получится ещё один костыль. Нужно что-то вроде:


// Декларация
type invokable($value): bool 
{
    return is_object($value) && method_exists($value, '__invoke');
}

// Алиас
typealias callable = invokable | array | string | \Closure;
Ну "клиентам" это не мешает видеть return докблок:

Принимающая сторона ничего не знает о том кто продьюснул коллекцию. Она знает только о том что ей нужна коллекция с определенными элементами.


без наличия деклараций, аналогичных инвариантам DbC — получится ещё один костыль

с инвариантами неплохо было бы, но суть то в том что бы обеспечить проверки на уровне системы типов. А контракты можно юнит тестами описать. Мне такой подход больше нравится.

Принимающая сторона ничего не знает о том кто продьюснул коллекцию. Она знает только о том что ей нужна коллекция с определенными элементами.

Имеется ввиду вызов от интерфейса, а не от реализации N метода? Тогда да, не отрицаю, возможно дженерики что-то и решат…


с инвариантами неплохо было бы, но суть то в том что бы обеспечить проверки на уровне системы типов. А контракты можно юнит тестами описать. Мне такой подход больше нравится.

А как тайпалиасами описать тип "callable", например? Кажется, что без императивщины, хотя бы в минимальном её проявлении это сделать невозможно. Если даже посмотреть на любой язык, где есть подобные штуки (ака Swift, Haskell (вот тут не уверен, могу ошибаться), TypeScript, FlowType и проч.) — их типы всё равно не полноценные. Они покрывают лишь набор булевых условий на соответствие встроенным типам. А более сложные вещи реализуются лишь в языках, вроде Irdis, о которых вообще никто не знает и не слышал =) И прошу заметить, там всё тоже вполне императивно.

Тоже думал о похожей штуке, чтобы была функция, которая возвращает соответствие типу.


Ну и до кучи: http://marc.info/?l=php-internals&m=148921424807417
(ничего не решили, обсуждение заглохло).

Ну «клиентам» это не мешает видеть return докблок:

1. У нас уже есть механизм типизации? Зачем для какого-то одного исключения использовать другой, менее удобный механизм типизации?
2. Я уже говорил выше, что Дженерики — не только массив. Другие механизмы использования Дженериков так не закостылишь.

НЛО прилетело и опубликовало эту надпись здесь

У меня уже давно не бывало такого. Нынче современные IDE светятся как новогодние ёлки (ну шторм, по крайней мере), если аннотация не соответствует типам или что-то идёт не так. А в случае наличие EA расширения — так ещё и чуть ли не код исправляет за тебя. Добавить к этому всякие CI-штуки, вроде скрутинизера… В результате, любую подобную ошибку ну просто невозможно не заметить. Всё окружение будет говорить о том, что есть несоответствие типов.

Когда что-то идёт не так, надо редактировать «плохой» код, а не огораживаться от него. Кроме данных не того типа, могут быть неправильные данные того типа (например, взял элемент массива, отредактировал его и вместо замены добавил на новое место).
Дженерики — это заметание сора под ковер. Тип проверять не надо, но надо быть уверенным в типобезопасности уже системы типов.
То есть если при ошибке в типе вы получите конкретную ошибку, то при ошибке системы типов вы получите «что-то пошло не так».
Дженерики — это заметание сора под ковер. Тип проверять не надо, но надо быть уверенным в типобезопасности уже системы типов.

Что? Вы пользовались когда-то языками с мощной типизацией? Например, C#. Они не оставляют никаких сомнений, там все надежно и прекрасно
Я рад, что Вы не испытываете никаких сомнений. Вам хорошо на свете.
Оператор… не подходит для передачи коллекций с большим количеством элементов. Он представляет собой синтаксический сахар для сворачивания-разворачивания ряда аргументов в объявлении функции или метода. В действительности, аргументы передаются по-отдельности, с соответствующим расходованием памяти и времени. Если бы свёрнутые аргументы реально передавались массивом, в сочетании со строгой типизацией можно было бы реализовать типизированные коллекции, но увы.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий