Ответ 1
Исходной ошибкой сборки была ошибка компилятора. Фактически, компилятор будет распознавать, что все экземпляры Element
совпадают, поскольку Element
не был перегружен как общий тип функции.
Упражнение состояло в том, чтобы написать мою собственную функцию map()
над Collection
(без использования каких-либо функциональных примитивов, таких как reduce()
). Он должен обрабатывать такой случай:
func square(_ input: Int) -> Int {
return input * input
}
let result = input.accumulate(square) // [1,2,3] => [1,4,9]
Моя первая попытка:
extension Collection {
func accumulate(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
Это отлично работает на игровой площадке, но не удается построить против тестов, давая ошибку:
Value of type '[Int]' has no member 'accumulate'
Решение состоит в обобщении метода accumulate
:
extension Collection {
func accumulate<T>(_ transform: (Element) -> T) -> [T] {
var array: [T] = []
for element in self {
array.append(transform(element))
}
return array
}
}
Я признаю, что общая версия менее ограничительна (не требует преобразования для возврата того же типа), но, учитывая, что тесты не требуют этой общности, почему компилятор?
Из любопытства я попытался:
extension Collection {
func accumulate<Element>(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
который выдает увлекательную ошибку сборки: '(Self.Element) -> Element' is not convertible to '(Element) -> Element'
в инструкции append()
.
Итак, компилятор (конечно) знает, что первый элемент - это Self.Element, но не относится к другому типу Element как к одному. Почему?
UPDATE:
На основе ответов выясняется, что отказ первой версии был ошибкой компилятора, исправленной в XCode 9.2 (я на 9.1).
Но все же я задавался вопросом, есть ли в
func accumulate(_ transform: (Element) -> Element) -> [Element]
он будет видеть два типа (Self.Element
и Element
) или признать, что они одинаковы.
Итак, я сделал этот тест:
let arr = [1,2,3]
arr.accumulate {
return String(describing: $0)
}
Разумеется, ожидаемая ошибка: error: cannot convert value of type 'String' to closure result type 'Int'
Итак, правильный ответ: компилятор будет обрабатывать ссылки на Element как одно и то же, если не существует общего типа, который перегружает имя.
Как ни странно, это удается:
[1,2,3].accumulate {
return String(describing: $0)
}
PS. Спасибо всем за ваш вклад! Награда была автоматически присуждена.
Исходной ошибкой сборки была ошибка компилятора. Фактически, компилятор будет распознавать, что все экземпляры Element
совпадают, поскольку Element
не был перегружен как общий тип функции.
О первом вопросе, работающем с Xcode 9.2 и Swift 4, я не получил никаких ошибок построения, таких как:
Значение типа '[Int]' не имеет члена 'accumulate'
так что:
var mystuff:[Int] = [1,2,3]
let result = mystuff.accumulate(square)
он просто дает мне правильный результат [1,4,9]
Во втором вопросе прототип функции ошибочен, вы должны попробовать Self.Element
:
extension Collection {
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
Я остановлюсь на вашем втором вопросе.
func accumulate<Element>(_ transform: (Element) -> Element) -> [Element]
Проблема с написанием этой подписи состоит в том, что у вас есть два разных типа с тем же именем.
Element
, который является вашим общим типом (бит между угловыми скобками).Self.Element
, то есть тип элементов в вашей коллекции, объявленный самим протоколом. Обычно вам не нужно явно писать часть Self.
, но поскольку ваш общий тип имеет то же имя, что и этот, Swift не может отличить их друг от друга.Разница более очевидна, если вы измените имя родового типа:
func accumulate<E>(_ transform: (E) -> E) -> [E]
Это эквивалентно версии accumulate<Element>
- изменение имени просто подчеркивает, что на самом деле происходит.
В более общем смысле Swift позволит вам называть свои типы тем, что вы пожелаете. Но если имя типа конфликтует с другим типом из другой области, то вам либо придется его устранить. Если вы не устраните двусмысленность, Swift выберет наиболее локальный матч. В вашей функции общий тип "наиболее локальный".
Представьте, что вы должны были определить свой собственный тип String:
struct String {
// ...
}
Это полностью допустимо, но если вы хотите использовать тип String, предоставляемый стандартной библиотекой Swift, вам необходимо устранить эту проблему следующим образом:
let my_string: String = String()
let swift_string: Swift.String = ""
И вот почему Андреа изменила подпись функции. Вам нужно сообщить компилятору, к которому относится тип "Элемент".
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]
В общем, я бы посоветовал не использовать общий тип имени другого типа, который вы используете. Это просто смущает всех. 😄
Я не уверен в вашем первом вопросе. Если он работает на игровой площадке, но не в ваших тестах, я бы предпочел сделать ее публичной. Тесты обычно определяются в отдельных модулях, и чтобы что-то было видно в другом модуле, оно должно быть объявлено общедоступным.
extension Collection {
public func accumulate //...
}
При реализации расширения для Collection
связанный тип коллекции будет по умолчанию Element
; Было бы путать назвать ваш родословный "Элемент" (func accumulate<Element>
), однако для вашего случая нет необходимости объявлять свою подпись метода следующим образом:
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]
Вместо этого он должен быть:
func accumulate(_ transform: (Element) -> Element) -> [Element]
Также, если вы хотите, чтобы ваш метод функционировал только для целых чисел, вы должны ограничить расширение, которое должно применяться только для коллекций BinaryInteger, как показано ниже:
extension Collection where Element: BinaryInteger {
func accumulate(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
Или для расширения области, это может быть Numeric вместо.