Почему требуется генерация, когда все типы уже определены?

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

Ответ 1

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

Ответ 2

  • О первом вопросе, работающем с 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
       }
    }
    

Ответ 3

Я остановлюсь на вашем втором вопросе.

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]

В общем, я бы посоветовал не использовать общий тип имени другого типа, который вы используете. Это просто смущает всех. 😄

Ответ 4

Я не уверен в вашем первом вопросе. Если он работает на игровой площадке, но не в ваших тестах, я бы предпочел сделать ее публичной. Тесты обычно определяются в отдельных модулях, и чтобы что-то было видно в другом модуле, оно должно быть объявлено общедоступным.

extension Collection {
  public func accumulate //...
}

Ответ 5

При реализации расширения для 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 вместо.