Сравните any P.Type и (any P).Type: какой из этих типов представляет метатип конкретного типа, соответствующего протоколу, а какой — метатип самого existential-типа?
any P.Type представляет метатип некоторого конкретного типа, соответствующего протоколу P. Такой метатип можно использовать, чтобы обращаться к требованиям протокола, объявленным как статические.
(any P).Type — это метатип самого existential-типа any P, то есть тип, описывающий контейнер existential-значения, а не конкретный тип, скрытый внутри него.
Протоколы в Swift используются не только как ограничения для generic-параметров, но и как типы значений через existential-модель. Поэтому язык должен различать конкретный тип, соответствующий протоколу, и значение-контейнер, внутри которого такой тип скрыт.
Явное написание any делает эту границу заметной: разработчик видит, где используется existential-тип, а где — метатип конкретного соответствующего типа. Это предотвращает ошибочное предположение, что любой метатип, связанный с протоколом, сохраняет сведения об одном и том же конкретном типе.
Метатипы применяются в фабриках, регистраторах типов и коде, работающем со статическими требованиями протоколов. Если перепутать any P.Type и (any P).Type, можно получить неверные ограничения или потерять возможность обратиться к статической части контракта.
Особенно важно, что existential-значение скрывает конкретный тип, но any P.Type описывает метатип этого скрытого конкретного типа. Это не то же самое, что метатип самого existential-контейнера.
Рассмотрим протокол со статическим требованием:
Cat.self имеет тип Cat.Type, но может быть передан как any Named.Type. Здесь any Named.Type означает: «метатип некоторого конкретного типа, соответствующего Named». Конкретный тип известен во время выполнения, поэтому вызов статического требования разрешается через соответствующее протоколу описание.
В записи (any Named).Type скобки меняют смысл. Она означает метатип типа any Named, то есть метатип existential-контейнера. Это описание самого existential-типа, а не универсальный метатип любого конкретного conforming-типа.
Практически различие можно сформулировать так:
any P.Type — «метатип некоторого конкретного T, где T: P»;(any P).Type — «метатип existential-типа any P».any P.Type не означает, что все значения такого типа относятся к одному заранее известному T. Каждый экземпляр может содержать метатип другого конкретного conforming-типа.
Это также не заменяет generic-параметр. Generic-функция сохраняет конкретный тип как параметр компиляции, тогда как any P.Type стирает его до existential-представления и оставляет только возможности, гарантированные протоколом.
В приложении есть реестр экранов, каждый экран соответствует протоколу Screen и предоставляет статическое имя. Реестру нужно принимать метатипы экранов, не создавая их экземпляры.
Вариант с any Screen.Type позволяет хранить метатипы разных конкретных экранов и получать общие статические сведения через протокол. Это удобнее, чем передавать экземпляры, когда создание объекта требует зависимостей или побочных эффектов.
Вариант с generic-параметром сохраняет конкретный тип и полезен, если фабрике нужно вернуть именно тот же тип или связать несколько аргументов с одним T. Минус — такой параметр не позволяет просто собрать значения разных типов в одной обычной коллекции без type erasure.
(any Screen).Type для этой задачи не подходит: он описывает метатип existential-типа, а не произвольный метатип конкретного экрана. Поэтому выбран any Screen.Type; результатом становится единый реестр метатипов, соответствующих протоколу.
1. Является ли any P.Type метатипом самого протокола P?
Нет. P.Type в историческом и контекстном употреблении часто воспринимается как «метатип, связанный с протоколом», но any P.Type точнее означает existential, содержащий метатип конкретного типа, соответствующего P. Например, метатипы Cat.Type и Dog.Type могут быть представлены значениями типа any Named.Type.
2. Гарантирует ли any P.Type, что два значения имеют один и тот же конкретный тип?
Нет. У двух значений типа any P.Type могут быть разные скрытые типы. Если нужно доказать, что два значения относятся к одному T, следует использовать generic-связь, например одну и ту же generic-переменную типа, а не два независимых existential-значения.
3. Можно ли через (any P).Type получить конкретный тип, скрытый внутри existential-значения?
Нет. Этот тип описывает existential-тип, а не раскрывает его внутренний concrete type. Для работы с конкретным скрытым типом Swift может временно открыть existential в подходящих generic-контекстах, но такое открытие не превращает (any P).Type в универсальную замену T.Type и не позволяет произвольно извлечь конкретный тип как обычное runtime-значение.