Зачем мне нужна перегрузка ‘<' для класса массива?

#arrays #sorting #swift

#массивы #сортировка #swift

Вопрос:

Я пытаюсь добавить функциональность в класс массива.
Поэтому я попытался добавить sort(), похожий на лексикон Ruby.
Для этой цели я выбрал имя ‘ricSort()’ из уважения к sort() Swift.

Но компилятор говорит, что не может найти перегрузку для ‘<‘, хотя ‘sort({$ 0, $ 1}’ сам по себе работает нормально.
Почему?

введите описание изображения здесь

 var myArray:Array = [5,4,3,2,1]
myArray.sort({$0 < $1}) <-- [1, 2, 3, 4, 5]
myArray.ricSort() <-- this doesn't work.
  

Комментарии:

1. Я не знаю swift, но я предполагаю, что использование вами $ 0 < $ 1 как-то недопустимо с точки зрения сортировки массивов. Вы пытаетесь использовать оператор «<» для чего-то, для чего он не поддерживает его использование, что наводит меня на мысль, что то, как вы это делаете, неправильно. Подробнее: goo.gl/mFt6bF

2. Вы пытаетесь расширить Array класс для любого возможного type , что он содержит. Некоторые типы могут быть несопоставимы с < , поэтому вам нужно ограничить Extension только сопоставимыми типами. Посмотрите, как указать, что type удерживаемое массивом сопоставимо.

3. Просто запуск ‘myArray.sort($0 < $1})’ — закрытие через сокращение является законным в Swift … работает, как указано выше. Но при использовании в функции расширения Swift этого не понимает. Вот в чем проблема.

Ответ №1:

Вот решение, близкое к тому, что вы ищете, за которым следует обсуждение.

 var a:Int[] = [5,4,3,2,1]

extension Array {

    func ricSort(fn: (lhs: T, rhs: T) -> Bool) -> T[] {
        let tempCopy = self.copy()
        tempCopy.sort(fn)
        return tempCopy
    }
}

var b = a.ricSort(<) // [1, 2, 3, 4, 5]
  

С исходным кодом есть две проблемы. Первая, довольно простая ошибка заключается в том, что Array.sort она не возвращает никакого значения вообще (представленного как () вызываемый void или Unit на некоторых других языках). Итак, ваша функция, которая заканчивается return self.sort({$0 < $1}) , на самом деле ничего не возвращает, что, я считаю, противоречит вашему намерению. Так вот почему это нужно return tempCopy вместо return self.sort(...) .

Эта версия, в отличие от вашей, создает копию массива для изменения и возвращает ее вместо этого. Вы можете легко изменить его, чтобы он сам мутировал (первая версия сообщения сделала это, если вы проверите историю редактирования). Некоторые люди утверждают, что поведение сортировки (изменение массива вместо возврата нового) нежелательно. Это поведение обсуждалось в некоторых списках разработчиков Apple. См . http://blog.human-friendly.com/swift-arrays-the-bugs-the-bad-and-the-ugly-incomplete

Другая проблема заключается в том, что компилятору недостаточно информации для генерации кода, который будет реализован ricSort , поэтому вы получаете ошибку типа. Похоже, вам интересно, почему он может работать при использовании myArray.sort , но не при попытке выполнить тот же код внутри функции в массиве.

Причина в том, что вы сообщили компилятору, почему myArray состоит из:

 var myArray:Array = [5,4,3,2,1]
  

Это сокращение для

 var myArray: Array<Int> = [5,4,3,2,1]
  

Другими словами, компилятор сделал вывод, что myArray состоит из Int , и так получилось, что Int соответствует Comparable протоколу, который предоставляет < оператор (см.: https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/Comparable.html#//apple_ref/swift/intf/Comparable)[1]. Из документации вы можете видеть, что < она имеет следующую подпись:

 @infix func < (lhs: Self, rhs: Self) -> Bool
  

В зависимости от того, на каких языках у вас есть опыт, вас может удивить, что < это определяется в терминах языка, а не просто как встроенный оператор. Но если вы подумаете об этом, < это просто функция, которая принимает два аргумента и возвращает true или false . Инфикс @ означает, что он может отображаться между двумя его функциями, поэтому вам не нужно писать < 1 2 .

(Тип «Self» здесь означает «независимо от типа, который реализует этот протокол», см. Объявление типа, связанного с протоколом, в https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_597)

Сравните это с сигнатурой Array.sort: isOrderedBefore: (T, T) -> Bool

Это общая подпись. К тому времени, когда компилятор работает над этой строкой кода, он знает, что реальная сигнатура isOrderedBefore: (Int, Int) -> Bool

Теперь задача компилятора проста, ему просто нужно выяснить, существует ли функция с именем < , соответствующим ожидаемой сигнатуре, а именно, которая принимает два значения типа Int и возвращает Bool . Очевидно < , что здесь соответствует сигнатуре, поэтому компилятор разрешает использовать функцию здесь. В нем достаточно информации, чтобы гарантировать, что < это будет работать для всех значений в массиве. Это в отличие от динамического языка, который не может этого предвидеть. Вы должны фактически попытаться выполнить сортировку, чтобы узнать, действительно ли типы могут быть отсортированы. Некоторые динамические языки, такие как JavaScript, будут предпринимать все возможные попытки продолжить работу без сбоев, чтобы такие выражения, как 0 < "1" правильно вычислять, в то время как другие, такие как Python и Ruby, будут выдавать исключение. Swift не делает ни того, ни другого: он не позволяет вам запускать программу, пока вы не исправите ошибку в своем коде.

Итак, почему не работает ricSort? Потому что нет информации о типе, с которой он мог бы работать, пока вы не создадите экземпляр определенного типа. Он не может определить, будет ли ricSort это правильно или нет.

Например, предположим, что вместо myArray , у меня было это:

 enum Color {
    case Red, Orange, Yellow, Green, Blue, Indigo, Violet
}

var myColors = [Color.Red, Color.Blue, Color.Green]
var sortedColors = myColors.ricSort() // Kaboom!
  

В этом случае myColors.ricSort произойдет сбой из-за ошибки типа, поскольку < не был определен для перечисления цветов. Это может произойти в динамических языках, но никогда не должно происходить в языках со сложными системами типов.

Могу ли я все еще использовать myColors.sort ? Конечно. Мне просто нужно определить функцию, которая принимает два цвета и возвращает затем в некотором порядке, который имеет смысл для моего домена (EM длина волны? Алфавитный порядок? Любимый цвет?):

 func colorComesBefore(lhs: Color, rhs: Color) -> Bool { ... }
  

Затем я могу передать это в: myColors.sort(colorComesBefore)

Надеюсь, это показывает, что для того, чтобы ricSort работать, нам нужно сконструировать его таким образом, чтобы его определение гарантировало, что при его компиляции можно будет показать, что оно правильное, без необходимости запускать его или писать модульные тесты.

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

Ответ №2:

Причина, по которой вы получаете ошибку, заключается в том, что компилятор не может гарантировать, что тип, хранящийся в массиве, можно сравнить с < оператором.

Вы можете увидеть такое же замыкание сортировки в массиве, тип которого можно сравнить, используя < Int :

 var list = [3,1,2]
list.sort {$0 < $1}
  

Но вы получите сообщение об ошибке, если попытаетесь использовать тип, который нельзя сравнить с < :

 var URL1 = NSURL()
var URL2 = NSURL()
var list = [URL1, URL2]
list.sort {$0 < $1} // error
  

Особенно со всем синтаксисом, который вы можете оставить в Swift, я не вижу причин определять метод для этого. Следующее допустимо и работает, как ожидалось:

 list.sort(<)
  

Вы можете сделать это, потому < что на самом деле определяет функцию, которая принимает два Ints и возвращает a Bool точно так же, как sort ожидает метод.