Упражнение состояло в том, чтобы написать мою собственную функцию 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. Спасибо всем за ваш вклад! Награда была автоматически присуждена.