Swift 2.2. Важнейшие нововведения

Original author: Piotr Sochalewski
  • Translation
Здравствуйте, уважаемые читатели!

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

Приятного чтения!


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

Здесь я упомяну несколько важнейших изменений, планируемых в Swift. Можете опробовать их прямо сейчас, скачав демонстрационную версию Swift 2.2, которая будет работать с Xcode 7.2 и 7.3-бета, если немного похимичить. Предварительно потребуется просто познакомиться с последней версией Swift, описанной на официальном сайте.

Операторы

В Swift 2.2 не будет операторов ++ и --. На первый взгляд меня это возмутило, но, фактически, в современном Swift они уже просто не нужны. Необходимость в инкрементах отпадает благодаря новому циклу for и функциям высшего порядка вроде map или filter. Разумеется, вы по-прежнему можете реализовывать инкремент сами, но уже иным способом.

func plusPlus() {
    var i = 0
    i++ // ВНИМАНИЕ: '++' нежелателен: в Swift 3 от него планируется избавиться
    i += 1 // '+=' и '-=' применяется для целочисленных типов или чисел с плавающей точкой 
 
    // succesor() и predecessor() применяются с индексными типами
    let alphabet = "abcdefghijklmnopqrstuvwxyz"
    let index = alphabet.characters.indexOf("u")
    if let index = index {
        alphabet.characters[index.successor()]
    }
}


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

C-подобные циклы

Такие унаследованные циклы – еще одна вещь, заимствованная из C. Удаляя for init; comparison; increment {}, мы также можем с легкостью удалить ++ и --. Но не переживайте – в Swift есть отличный и очень удобный цикл for-in.

func forLoop() {
    // ВНИМАНИЕ: C-подобная инструкция for нежелательна
    // и будет удалена в следующей версии Swift
    for var i = 1; i <= 10; i += 1 {
        print("I'm number \(i)")
    }
    
    // Инновационный стиль Swift работает нормально:
    for i in 1...10 {
        print("I'm number \(i)")
    }
}



Срезы

Вот чего я ждал целую вечность. Массивы в Swift 2.2 имеют функцию removeFirst()
. Ее можно было без труда написать и самому в качестве расширения, но теперь она предоставляется из коробки. Функция очень умная, и, даже хотя ваш массив может быть переполнен опционалами, она просто удаляет первый элемент, а не оставляет его в виде nil
. Обожаю.

func slices() {
    var array = ["First", "Second", "Third", "Fourth"]
    array.removeLast() // она существовала всегда, но…
    array.removeFirst() // родилась заново (каждый n-ный элемент становится n-1)
}


Сравнение кортежей

Может быть, это и не сенсация, но странно, что такая возможность до сих пор отсутствовала. В Swift 2.2 и далее можно сравнивать кортежи, содержащие до 6 элементов Equatable
.

func tuples() {
    let tuple1 = (1, true, 3, 4, 5, "a")
    let tuple2 = (1, false, 3, 4, 5, "a")
    let equalTuples = tuple1 == tuple2
    print(equalTuples) // false
}


Больше никакого карринга

Возможно, вы расстроитесь, если по-настоящему любили каррированные функции, но я не расстроился. В Swift 2.2 они переосмыслены и станут немного более удобочитаемыми.

// ВНИМАНИЕ: синтаксис объявления каррированных функций будет упразднен 
// в новой версии Swift; пользуйтесь списком с одним параметром
func noMoreCurryingThisWay(a: Int)(b: Int) -> Int {
    return a + b
}
 
func curryThisWay(a: Int) -> (Int) -> Int {
    return { (b: Int) -> Int in
        return a + b
    }
}



Проверка версии Swift

Очень интересная возможность, правда, надеюсь, не придется часто ею пользоваться. Я предпочитаю рефакторить код, совместимый со Swift 2, до Swift 2.2, а не поддерживать сразу две (или более) версий. Но в некоторых случаях эта возможность может пригодиться (например, пишем каркас, который должен работать и с 2.0, и с 2.2 – а, возможно, даже с 3.0). Попробуйте.

func swiftVer() {
    var cuteNumber = 7
    #if swift(>=2.2)
        print("Good morning, I'm brand new Swift")
        cuteNumber += 1
    #else
        print("Hi, I'm elder than my brother, but can ++")
        cuteNumber++
    #endif
    
    // Такой стыд, '#if swift' выглядит как старомодный макрос из Obj-C,
    // а не по-свифтовски как '#available'. Обратите внимание на этот префикс '#'.
    // Выглядит старомодно, а я хотел бы, чтобы было вот так:
    /*
     
    if #swift(>=2.2) {
    } else {
    }
     
    */
}


Селектор

Еще один макро-прием, которого я избегаю. В Swift 2.2 нельзя указывать селектор как строку. В открытом обсуждении был выбран вариант #selector
, а не Selector()
, что напоминает создание экземпляра (и также нежелательно). Фактически, этот вариант все равно лучше, чем просто строка, благодаря автозаершению; вероятность опечатки минимальна.

func selector() {
    let button = UIButton()
    // ВНИМАНИЕ: использование строкового литерала для селекторов Objective-C
    // нежелательно; вместо этого используйте '#selector' 
    button.addTarget(self, action: "forLoop", forControlEvents: .TouchUpInside)
    
    // На самом деле '#selector' работает нормально
    button.addTarget(self, action: #selector(forLoop), forControlEvents: .TouchUpInside)
}


Заключение

Это не все изменения, ожидаемые в Swift 2.2, но, на мой взгляд, наиболее важные. Рекомендую заглядывать в официальный свифтовский журнал изменений (правда, на деле это всего лишь «дорожная карта») и в раздел swift-evolution на GitHub Apple, где озвучиваются все предложения об эволюции языка.

Меня очень вдохновляет такое развитие событий, я предвкушаю грядущие изменения. Swift современный и крутой. Приходится быть современным, если хочешь перерасти Obj-C. Нужна решимость, чтобы избавиться от этих C-подобных циклов, использующихся с 1972 года. Будем надеяться на лучшее!

Comments 44

    0
    Интересно, а что в итоге то с макросами, я вот вижу что-то на них похожее, но не помню, чтобы можно было их создавать, Тоже самое касается атрибутов. Это вообще возможно?
      0
      Эм… Apple убирают из языка ++ / --, for(;;) — они решили превратить swift в Object Pascal? :)
        0
        Останутся += / -= и for-in.

        Причем последний можно использовать с массивами:
        for i in 1...5

        а массивы можно создавать с помощью stride:
        for i in 1.stride(to:5, by: 1)
          0
          Дык в object Pascal также можно:

          for i in [1..5] do
          +1
          Бред, конечно.
          Циклы с счетчиком, изменяемым внутри тела — обычное дело в общем-то. В том же Python каждый раз эмулировать такое через while достало неимоверно.
          0
          Swift современный и крутой. Приходится быть современным, если хочешь перерасти Obj-C. Нужна решимость, чтобы избавиться от этих C-подобных циклов, использующихся с 1972 года. Будем надеяться на лучшее!

          Я вот например считаю что перерастать Objective-C не требуется, это не некая устаревшая технология, а отличный инструмент со своими сильными и слабыми сторонами, как и Swift.

          Хотя отказ от c-style циклов и инкрементов имеет под собой основу: инкремент в Swift представляет собой чисто синтаксическую возможность и x++ и x += 1 по сути ничем не различаются.
          С циклами все более странно, в принципе в классических for циклах нет ничего плохого, но реализованы они в Swift так что
          for i in 0 ..< x
          работает быстрее чем
          for var i = 0; i < x; i++
          возможно стоит оптимизировать c-style циклы, а не убирать их из языка. Кроме того, for in не поддерживает декремент.
            0
            for i in 10.stride(to:5, by: -1)
              0
              Согласен, но этот цикл по идее должен на каждом шаге вызывать next() проверять что следующий элемент существует и выполнять тело цикла если это так. Что совсем не тоже самое по скорости чем уменьшать значение i на 1 и проверять что оно не стало меньше нуля.
                0
                Охренеть как удобно
              0
              Кстати, там за бортом осталась поддержка swift test, как минимум. Для не OsX платформ это точно актуально.
                0
                > В Swift 2.2 и далее можно сравнивать кортежи, содержащие до 6 элементов Equatable
                Почему именно 6 интересно, зачем было делать это магическое число и можно ли сравнивать вложенные кортежи?
                  +3
                  С выходом 7-ого iPhone число элементов будет ++. Пардон, += 1
                    0
                    Не знаю как в Swift, но в C# количество элементов кортежа (тип Tuple) тоже ограничено.
                    В C# это связано с тем, что нет генериков с переменным числом параметров, и вообще вариадики поддерживаются только гомогенные (т. е. одного типа). Поэтому в C# определены сразу несколько типов Tuple: с одним, двумя итд. параметрами.
                    Если мне память не изменяет, в Swift система типов устроена сходным образом (нет генериков-вариадиков).
                      0
                      Кстати говоря, еще ни разу не видел оправданного использования Tuple в C#. Обычно его используют только потому, что лень создавать именованный класс, но чтение и рефакторинг такого кода превращается в пытку. Поэтому обычно лучше использовать анонимные классы с именованными полями, или же не лениться и объявить класс, если его нужно пробрасывать между методами. В крайнем случае для 2 элементов сойдет KeyValuePair.
                        0
                        Я тоже как-то не встречал. Пару раз пытался по опыту других языков их воткнуть, но получалось уж слишком монструозно :)
                          0
                          Без синтаксической поддержки языка это не более чем просто класс, аналогично не нашёл применения, в контексте метода куда удобнее анонимные типы, в контексте класса — вложенные классы. К тому же отсутсвие вывода типов в конструкторах зашумляет код. Радует что в списке фич 7 версии языка находится в разделе "Strong interest"
                    +5
                    Что-то все повыкидывали.
                    Хоть я и не писал на Swift, но тенденция выкидывать общепринятые устоявшиеся за десятилетия и понятные любому программисту конструкции (типа инкремента) ИМХО странная. Это все равно что из математики выкинуть значок квадратного корня, сославшись на то что можно пользоваться возведением в степень 1/2.
                      +2
                      В отличие от математики, программный код разбирает компьютер, а находящиеся по эту сторону монитора не желают ни вводить с клавиатуры длинные последовательности, ни ждать, пока ИИ сможет понимать код, написанный на математическом языке.
                      Каждая фича в языке, каждая строчка в компиляторе может стоить немалых денег в поддержке. "Общепринятые" вещи из Си отсутствуют во многих языках, на которых выросло поколение авторов Swift, языков, начавших активно выходить за пределы академической среды — функциональных языков, в основном.
                      Swift — очень современный в этом плане язык, авторы активно движут его к зарекомендовавшим себя и модным ныне практикам ФП, но при этом в нем слишком много фич (а особенно синтаксиса) — скорее всего поэтому авторы убирают все, что в их идеальном стиле не используется, но при этом не ограничивает круг решаемых задач.
                      Важно понимать, что идеология Swift подразумевает написание идеоматического высокоуровнего высокодекларативного кода, который затем трансформируется очень умным компилятором в эффективный машинный код, который предположительно должен быть таким же быстрым, как написанный с использованием таких фич, как инкремент и цикл for в стиле Си.
                        0
                        Конечно авторы языка на то и авторы, чтобы решать каким язык должен быть… это их право. Но у меня другой подход к дизайну языков программирования. Я бесконечно уважаю и доверяю Программисту, и считаю что даже если одному программисту из миллиона понадобится для каких-то неведомых мне Хакерских целей та или иная фича — она должна быть в языке. Пусть инкремент, пусть что-то низкоуровневое или даже оператор goto… каждый программист — Творец, и не мое дело решать чем можно пользоваться а чем нельзя. Вот как-то так))
                          +1
                          А потом другим программистам наслаждаться поддержкой кода таких творцов. Нужно выходить за рамки — используйте низкоуровневые языки и наслаждайтесь стрельбой по ногам. А в данном случае язык рассчитан на толпы не очень хороших программистов, код которых должен и выглядеть и работать максимально качественно. В этом случае, чем жестче язык/фреймворк, тем лучше.
                            0
                            Да я все это прекрасно понимаю…
                            Но жить-то станет скучнее, программирование превратится из Хакерства в унылую конвейерную работу за копейки. Мечта менеждеров, да… но программистам-то это зачем?
                              0
                              Каждому делу свой инструмент только и всего :) Хакерство и творчество никуда не денется. Всегда нужны и художники и маляры, а инструменты у них в чем-то похожи, но очень разные.
                              0
                              А потом другим программистам наслаждаться поддержкой кода таких творцов.

                              Вы всерьёз считаете, что конструкции типа
                              for i in 1000000.stride(to:1, by: -1)
                              читаемы и удобнее в поддержке, чем примитивный for-цикл?
                              Или что операторы инкремента/декремента могут стрелять по ногам?
                              Понятно, что авторы языка решают что оставлять, но аргументы на тему, что конеретно эти изменения хоть как-то способны улучшить качество кода, выглядят очень странно.
                              Ладно бы все, как один, написали: фиг с ним с for, будем хвостовую рекурсию использовать… но нет, все про этот жуткий вариант со stride, который ещё и память на абсолютно ненужную последовательность тратить будет.
                                +2
                                Это не все, это я сразу в несколько веток ответил, потому что страйд не самая широко известная штука.
                                А касательно памяти — вы уверены, что нет никакой оптимизации на уровне компилятора?
                                  0
                                  stride — это способ генерации последовательности/массива, поэтому использовать его для замены счётчика итераций значит использовать его не по назначению. Я не уверен в отсутствии оптимизаций на этот случай на уровне компилятора, но, честно говоря, присутствие таких оптимизаций было бы нелогичным. Классический цикл for отсутствует во многих функциональных языках, но в любом из них генерация списка чисел для обхода считается моветоном.
                        +3
                        Вопросов больше, чем ответов.

                        • Как без С-style цикла сделать итерацию с шагом, отличным от 1?
                        • Почему функция `removeFirst` это срез? Общепринятый смысл термина «срез» — это подмножество массива.
                        • Зачем убирать более удобную версию записи каррирования? Новый синтаксис с 3 и более параметрами будет выглядеть монструозно.
                        • Почему сравнивать можно только кортежи до 6 элементов?
                          0
                          Фичу с кортежами небось прикрутили сбоку изолентой :). В C# вроде бы максимальная длина кортежа равна 8, здесь 6…
                          Догадайтесь почему и откуда такие магические числа.
                            0
                            В C# есть несколько стандартных классов типа Func, Action и Tuple, у которых есть "варианты" с различным количеством аргументов. Причина в том, что когда в .NET 2.0 добавили обобщения, особой необходимости в variadic type arguments еще не было, а потом добавить это без кардинального изменения рантайма оказалось невозможно. При необходимости кортеж произвольной длины можно создать, используя в качестве последнего элемента вложенный кортеж. И тогда они будут-таки рекурсивно сравниваться, сколько их ни навтыкай.

                            Однако вопрос в другом. В Swift вообще нельзя сделать кортеж из более, чем 6 элементов? Или все же можно, но он не будет сравниваться? Если так, то это однозначно сознательное ограничение, но его смысл от меня ускользает. Производительность?
                            +1
                            Как без С-style цикла сделать итерацию с шагом, отличным от 1?

                            Скорее всего, авторы хотят сделать акцент на техниках функционального программирования и эти юзкейсы должны покрываться фильтром по индексу, например (псевдокод): collection.filter(eventh).map(doSomething).reduce(calcResult)

                            Зачем убирать более удобную версию записи каррирования? Новый синтаксис с 3 и более параметрами будет выглядеть монструозно.

                            В Swift слишком много синтаксиса. Скорее всего, синтаксис каррирования усложнял парсер, так как требовал разрешения неоднозначностей, а может просто прибили как наименее полезный синтаксис по версии авторов.
                            "Новый" синтаксис — явная форма возврата функции. На практике я довольно редко вижу применение функции больше, чем в две стадии, так что в каком-то смысле компромисс для меня очевидный.
                              0
                              Акцент получается довольно странный. Циклы и инкременты — слишком императивно, каррирование — слишком функционально. Прагматичность — это хорошо, но для языка и платформы ортогональность не менее важны.
                                +1
                                Я думаю, упор не на функциональное программирование как таковое, а скорее на декларативность, достижимую с помощью средств, заимствованных из функционального программирования. Ну и на value semantics. И умный компилятор, который магически улучшает говнокод.
                                С таким компилятором, кстати, чем меньше свободы людям, тем больше оптимизатору.
                                Получается очень хороший набор базовых постулатов, с синергией. И очень в духе Apple "Мы делаем лучшие чистилки для апельсинов, потому что мы лучше всех знаем, как чистить апельсины." Можно ли чистить красные апельсины? Я думаю, для этого найдется своя Cydia.
                                +2
                                Как без С-style цикла сделать итерацию с шагом, отличным от 1?

                                проще
                                for i in 100.stride(to:500, by: 25)
                                  0
                                  Вырвиглазно :(

                                  for(i = 100; i < 500; i += 25)

                                  – выглядит проще и компактнее.
                                +1
                                Зачем убирать более удобную версию записи каррирования?

                                См. в motivation

                                Почему сравнивать можно только кортежи до 6 элементов?

                                Чтобы соблюсти баланс между удобством и размером библиотеки. Текущее раширение до 6 увеличило размер на 1,4 %. 12 элементов увелчивают размер на 5,5 % (+175 КБ).

                                Почему функция `removeFirst` это срез? Общепринятый смысл термина «срез» — это подмножество массива.

                                Тут просто про свифтовое ArraySlice, Sliceable и пр. базовые -slice типы (смысл которых, что они не копируют элементы, а юзают существующую память, отсюда и название и эффективность в O(1)). Вот в них и добавили removeFirst. А обычные массивы все это наследуют по иерархии.
                                  0
                                  Что-то ребята поторопились с дизайном языка.

                                  Частичное применение функции, имхо, сделали только потому, что это модно. Теперь же оказалось, что никто это не использует потому, что слишком сложно.

                                  По поводу кортежей на stackoverflow подсказывают, что в него можно засунуть до 1948 (!!!) элементов. Зачем это нужно, особенно если они беспокоятся по поводу размера библиотеки?
                                  +1
                                  Как без С-style цикла сделать итерацию с шагом, отличным от 1?

                                  Народ писал про stride, но, положа руку на сердце, я за 10 лет ни разу такой цикл не писал.

                                  К слову, Андрюх, тут, скорее, можно возразить про более актуальное «а как узнать индекс текущего элемента, если убрали олдскульный for», но помимо классического:
                                  for item in items { }
                                  

                                  …есть менее известный:
                                  for (index, item) in items.enumerate() { }
                                  

                                  В том же шарпе такое только, если не изменяет память в «.Select( (index, item) => { })», а вот в обычном foreach часто не хватает. Свифт, в общем, молодец.
                                    0
                                    Прямо дежавю с Python. И это скорее хорошо.
                                  +1
                                  Оффтопик. К чему эти точки и запятые в начале строк, а не в конце?
                                    0
                                    if swift(>=2.2)

                                    Разве нельзя было добавить волшебную переменную __version__?
                                      +1
                                      Боже мой, что это? Унарный оператор сравнения?
                                        +1
                                        Это не просто if, а волшебный #if

                                        Кстати, __такие__ волшебные переменные теперь заменены на #такие
                                        У Erica Sadun в бложике много интересного.
                                      • UFO just landed and posted this here
                                          +1
                                          Сказывается любовь к странным повторяемым символам пунктуации, теперь в моде -> вместо []

                                          func curryThisWay(a: Int) -> (Int) -> Int {
                                          return { (b: Int) -> Int in
                                          return a + b
                                          }
                                          }
                                            0
                                            Я уж надеялся, что начиная с 2й версии на нём можно пилить что-то серьёзное и новый проект можно стартовать на Swift. Но нет, подожду-ка я до 4й :)

                                            Only users with full accounts can post comments. Log in, please.