Google объявил, что TensorFlow переезжает на Swift. Так что отложите все свои дела, выбросьте Python и срочно учите Swift. А язык, надо сказать, местами довольно странный. Для затравки посмотрите небольшую презентацию с объяснением, почему Swift и как с этим связан TensorFlow: Разработчики TensorFlow, конечно, пока не забывают Python, но основой фреймворка станет именно Swift. Хотя в нем можно будет писать python-подобный код. Но исполняться он все равно будет интерпретатором Python, а это снова значит медленно, непараллельно, неэффективно по памяти, без контроля типов и все прочее. Поэтому учим Swift с нуля. Ну, не совсем с нуля: предполагается, что вы уже хорошо программируете на Python'е, и поэтому многие конструкции Swift далее будут описаны в сравнении с аналогичными Python'овскими конструкциями.
Статья ни в коем случае не претендует на подробное описание языка. Это лишь первое весьма поверхностное знакомство с основными возможностями языка для тех, кто знает Python. Swift — довольно новый язык. Это хорошо, потому что он основан на обширной базе более ранних языков. Но в то же время и очень плохо, потому что он пока еще не лишен совсем “детских болезней”. Поэтому язык очень быстро эволюционирует.
Несмотря на то, что в интернете полно статей и туториалов по Swift — все они уже устарели. Многочисленные рецепты со StackOverflow вам скорее всего тоже не подойдут, потому что они относятся к предыдущим версиям языка. Хронология последних событий: в марте 2016 года вышел Swift 2.2, а в сентябре — уже “сильно другой” Swift 3, через год — “снова другой” Swift 4. Текущая версия 4.1, хотя Swift for Tensorflow — это уже 4.2-dev. До конца года выйдет Swift 5, в котором будет еще больше нововведений даже в самом языке, не говоря уже о библиотеках. В общем, TLDR: язык пока не готов к серьезной разработке для data science. Поэтому я позволил себе потратить лишь два часа на ознакомление с языком в его текущем виде, чтобы через полгода легче было погружаться в Swift 5 с уже новой версией TensorFlow. Все переменные строго типизированы, поэтому при объявлении придется указывать тип данных. К счастью, компилятор умеет определять тип по начальному значению. Как вы уже догадались, константы объявляются с помощью let, а переменные через var. В качестве хорошего тона и для оптимизации вычислений для всех значений, которые не будут в процессе исполнения программы меняться, рекомендуется именно let. В остальном, все просто и понятно. В Python'е есть slice, а в Swift'е целая пачка типов Range: открытый, закрытый, неполный снизу и т.д.
Литералами они задаются довольно кратко (но можно было бы и еще короче): Для задания диапазонов с шагом, отличающимся от единицы, или с нецелыми числами можно использовать функцию stride: или Догадаться невозможно, нужно просто знать, что в первом случае (с to) диапазон открыт справа (т.е. 0.5 не включается). В каждом языке есть своя чудовищная глупость. Разработчики Swift'а решили, что у них это будут строки. Во-первых, есть два строковых типа String и Substring. Они очень похожи, но отличаются лишь тем, что у Substring нет своей области памяти, и она всегда указывает на кусок памяти какой-то String. Идея понятная и правильная, но все эти нюансы можно было легко скрыть в реализации String. Дальше хуже. Как получить Substring из String? Вы думаете, тут есть что-нибудь как в Python'е — str[1:10]. Ничего подобного! Нельзя индексировать строки целыми числами. А как надо? Я не шучу. Это официальный способ работы со строками. У каждой дурацкой идеи есть длинное и бессмысленное объяснение. Этот случай не стал исключением. Обратите еще раз внимание на строку str[str.index(str.startIndex, offsetBy: 1) ..< str.index(str.startIndex, offsetBy: 10)]. В ней инвариантно все, кроме двух целых чисел. Иными словами, из 87 символов 84 лишние! Чтобы было по-людски, пишем extension для стандартного типа String: Запускаем… Не работает! Компилятор ругается: Дело в том, что в Swift есть явный хардкод, запрещающий создавать метод subscript, который принимает диапазон из целых чисел. Ладно, пойдем другим путем, в 10 раз длиннее, видимо таков уж Swift-way: И в качестве упражнения скопируйте весь этот текст еще раз для типа Substring. Язык не для краткости, это уже понятно. Зато теперь можно нормально работать со строками: Неизменяемая последовательность значений, или tuple, в Swift устроена немного иначе, чем в Python'е. Здесь это скорее похоже на смесь tuple и namedtuple. А вот распаковывать tuple в аргументы функции нельзя. Раньше было можно. Потом запретили. Возможно, в будущем обратно введут. Массив (Array) похож на python'овский list тем, что его размер можно менять, однако все элементы массива должны иметь один тип данных. С Setами та же история: как и в python'овском множестве можно менять состав элементов, но не тип. В словаре придется определить два типа: для ключей и для элементов. Кстати, если попробуете проитерироваться по словарю самым ожидаемым способом: то неожиданно получите кучку предупреждений от компилятора, потому что тип значений в словаре fullDict на самом деле не Int, а Optional<Int> (то есть может быть nil или int). Про Optional поговорим отдельно, а итерироваться удобней tuple'ами: Стандартный обход коллекции: Удобно работать и с диапазонами: Если индексы нужны выборочно, что конструкция резко удлиняется: Еще есть Все, как и ожидалось: Видимое отличие от Python можно заметить в том, что у аргумента может быть не только имя и тип, но и метка: Есть лямбды, здесь они называются closure Естественно, closure можно передавать в функции. И вот тут открывается новая внезапность: Выглядит как определение функции, только без слова func. Но на самом деле это вызов функции someFunc, которой в качестве последнего аргумента передается closure, заданная в фигурных скобках. Кстати, если closure выступает единственным аргументом функции, то круглые скобки можно опустить. Для создания сложных типов данных предусмотрены классы и структуры: С виду разницы никакой, но она все же есть: Как вы уже знаете по строкам, у классов и структур есть удобный метод subscript (аналог python'овских __getitem__ и __setitem__), позволяющий индексировать данные, чтобы вместо: писать более компактное: Реализуется он примерно так: Вы, наверное, спросите: что за newValue такое? А это еще одна неявная конвенция — если для set'а не заданы аргументы, то значение передается через переменную newValue.
Так, а что означает восклицательный знак после myData[i]? В строго типизированном языке нужен особый способ работы с отсутствующими значениями. В Swift для этого есть Optional, который принимает значение определенного при декларации переменной типа или значение nil. Как с этим работать? Оператор ! предназначен для принудительной распаковки (англ. “force unwrapping”) значения. Если при этом opt был nil, вы получите runtime crash. Другая, более рекомендуемая конструкция, — блок if-let: Для разворачивания опционала с присвоением ему значения по умолчанию существует оператор ??, который незамысловато называется nil-coalescing operator. Кроме того, Swift позволяет удобно получить значение атрибута, даже если он упакован в глубине сложной структуры: В Swift for Tensorflow заявлена работа с Python'ом, чтобы можно было писать Python-подобный код: Но сейчас так не работает и можно писать только вот так: Когда будет сделано по-нормальному, пока не известно, потому что нужны изменения в языке, а предложение на изменение еще даже не подано, хотя активно обсуждается (уже в третий раз и пока безрезультатно). Кроме того, сейчас Swift умеет работать только с Python 2.7, установленным в /usr/local/lib/python27 (опять хардкод). Ни с какими виртуальными средами тоже не совместим. В виду имеющейся разницы между Python 2 и 3 с точки зрения С-структур данных и C-вызовов в ближайшее время эта проблема также не разрешится. Наконец-то добрались до главного, ради чего все и затевалось. Начнем с перемножения матриц: Выглядит точно красивее, короче и понятнее, чем на Python'е c tf.while_loop вкупе с созданием сессии и инициализацией переменных. Вот только пока медленнее, причем в разы. И GPU, конечно же, не поддерживается. Кстати, для матричного умножения надо использовать не * и не @, а замечательный знак ⊗. Попробуйте ввести его с клавиатуры. Давайте уже сделаем нейросеточку!.. Хотя не сделаем: документации нет, готовых слоев нет, оптимизаторов нет, — в общем, ничего еще нет. Само собой, можно вручную перемножать тензоры, рассчитывать градиенты и изменение весов (см. вышеприведенное видео и единственный пример). Но этого мы делать, конечно, не будем.  Язык интересный, вот только для реального применения в data science пока не готов. Подождем.