1

Рассмотрим этот код:Почему мы не можем передавать типы протоколов со связанными типами, но добиваться такого же эффекта с помощью дженериков?

extension Collection { 
    func foo() -> Int { 
     if self.first is Collection { 
      return (self.first as! Collection).underestimatedCount // ERROR 
     } 
     else { 
      return self.underestimatedCount 
     } 
    } 
} 

Получим страшный и, по-видимому, широко озадачивает:

протокол «Collection» может быть использована только в качестве общего ограничения, поскольку он имеет Самость или связанных с ними требований типа.

Однако это счастливо компилирует:

func foo<C: Collection>(_ c: C) -> Int where C.Iterator.Element: Collection { 
    if let first = c.first { 
     return first.underestimatedCount // * 
    } else { 
     return c.underestimatedCount 
    } 
} 

Почему ?!

В частности, компилятор не знать * как соответствующие типы (тип) first были реализованы; он получает только обещание, что они были (потому что у любого объекта типа Collectionесть, чтобы реализовать их). Эта же гарантия существует в первом примере! Так почему компилятор жалуется на одного, а не на другого?

Мой вопрос: в строке *, что компилятор знает, что его нет в строке ERROR?

+0

Все объяснения приведенной ошибки Я нашел просмотр [SO], где в основном говорят: «Мы не знаем значений для связанных типов, так как мы можем их отличить?». По этому объяснению, я не думаю, что второй образец должен скомпилировать, следовательно, мой вопрос. – Raphael

ответ

3

протокола типизированного значения представлены с использованием «экзистенциального контейнера» (см this great WWDC talk на них, или on Youtube), который состоит из значений буфера фиксированного размера для того, чтобы хранить значение (если размер значения превышает это, он будет выделять кучу), указатель на таблицу свидетелей протокола для реализации методов поиска и указатель на таблицу показателей значения, чтобы управлять временем жизни значения.

Unspecialized generics используют практически тот же формат (я вхожу в это немного глубже in this Q&A) - когда они вызываются, указатели на протокол и таблицы значений значения передаются функции, а само значение сохраняется локально внутри функции, используя буфер значений, который будет выделяться для значений, больших этого буфера.

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

Вот отрывок из дженериков манифеста «Generalized existentials», которая обсуждает, как это может работать на практике:

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

protocol IteratorProtocol { 
    associatedtype Element 
    mutating func next() -> Element? 
} 

let it: IteratorProtocol = ... 
it.next() // if this is permitted, it could return an "Any?", i.e., the existential that wraps the actual element 

Кроме того, разумно хотеть, чтобы ограничить связанные типов экзистенциальным, например, «А Sequence чей тип элемента String» может быть выражено, помещая где положение в protocol<...> или Any<...> (за «Переименование protocol<...>Any<...> к»):

let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"] 

Ведущий . показывает, что мы говорим о динамическом типе, , т. е. Self, который соответствует протоколу Sequence. Нет причин, по которым мы не можем поддерживать произвольные where статьи в пределах Any<...>.

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

+0

Еще раз спасибо. Суммированные типы протоколов обрабатываются по-разному, когда они появляются в экзистенциальной или общей форме. Верный? (Хорошие ссылки!) – Raphael

+0

После прочтения пункта 4 в ответе вы ссылаетесь, разрешение перегрузки также кажется одной из вещей, которые меня отключают. Основное правило «выбирает наиболее подходящую реализацию», кажется, нарушено там (Swift предпочитает не общие для общих функций). – Raphael

+1

@Raphael. Неспециализированные общие типы и типы протоколов в основном обрабатываются одинаково (с той лишь разницей, что дженерики не используют явные экзистенциальные контейнеры, так как рассматривают общую функцию с несколькими параметрами типа, общий заполнитель «T» - указатели на таблицы свидетелей протокола и таблицы значений значения должны быть переданы один раз, а не для каждого параметра). Когда дело доходит до * специализированных * общих функций, очевидно, что они обрабатываются совершенно по-другому, так как параметры могут быть просто переданы по значению - и вызывающий узнает статический конкретный тип. – Hamish

Смежные вопросы