В серии про надежное программирование [1], [2] остался незаслуженно забытым Swift.
Я, честно говоря, просто не считал его кроссплатформенным, а работающим исключительно для macOS/iOS.
Тут случайно выяснилось, что Swift также поддерживается такой средой разработки, как RemObjects Elements.
Оказалось, у неё Универсальный Компилятор. Умеет компилировать программы на C#, Go, Java, Oxygene Object Pascal, Swift для: Android, Cocoa (MacOS, iOS, tvOS), JVM, Linux (x64, armv6, aarch64), .NET/.NET Core/Mono, Native Windows(x86/x64), WebAssembly.
Причем делает это практически в любых комбинациях язык -> целевая система! Например, можно написать программу на Java, которая будет использовать WPF для целевой платформы .NET, и это все есть в примерах, идущих в поставке.
Итак, представляю мини-заметку про RemObjects Elements, а заодно про надежность двух поддерживаемых в ней языков — Swift и Oxygene.
Рисунок с сайта radionetplus
А еще в дополнение к обсуждениям о проблеме интероперабельности разных сред исполнения — нативных, JVM, .NET, GC, ARC она оказалась просто невероятной, причем в обоих смыслах — так как при обсуждениях пришли к тому, что нормальная ИО невозможна.
Вкратце о RemObjects Elements
Это либо независимая среда разработки с отладчиком, в том числе и удаленным, и автодополнением кода для Windows или macOS, либо же интегрируется с Visual Studio, а для Cocoa обязателен XCode. Для Swift’a она бесплатна, для остального стоит от 199$ за версию для личных проектов, до 799$ за коммерческую лицензию для всех языков. За работу с клиент — серверными СУБД придется доплатить уже существенно.
Water — так называется версия для Windows, довольно аскетична, визуального редактора форм в ней нет, но симпатизирует строгостью, да и установщик занимает всего 700МБ, не включая конечно же JRE, .NET, Android NDK итп
Ругать её, или сильно хвалить я не буду, свои функции выполняет, не падала ни разу (но память течет).
Бонусом к IDE идет много:
- минимальный профайлер (в Water не работает, только текстовый файл вызовов ф-ций)
- обфускатор
- поддержка юнит-тестов
- межъязыковой транслятор исходного кода с поддержкой C#, Swift, Java, Delphi, C, Obj-C в Oxygene, C#, Swift, Java, Go
- утилита импорта для Go-библиотек с зависимостями. Кстати Go тулчейн пока в Бете
- библиотека для аспектного-ориентированного программирования
- некоторая поддержка миграции с Delphi/VCL
Интероперабельность
Конечно, есть небольшой подвох для тех, кто думает что (с) можно просто взять и перенести любую программу на другую платформу.
ИО здесь заключается в том, что программа умеет использовать только лишь возможности своей RTL _и_ целевой платформы для всех языков и их комбинаций. То есть для цели .NET можно использовать WPF, но не rt.jar, а для Native — только WinAPI или GTK соответственно. Но базовая библиотека, написанная на Oxygene, доступна везде, как и прослойка миграции с Delphi — кстати они доступны на гитхабе.
Проект же может включать в себя подпроекты на разных языках, поддерживаемых RO. И с импортом внешних библиотек тоже есть проработанные варианты.
Управление памятью будет использоваться от целевой платформы, например ARC для Cocoa. В детали я не вдавался, для Native по умолчанию используется GC, хотя есть выбор — проверено, есть в моем проекте на github.
Добавлю одно наблюдение. Если программа писалась под ручное управление памятью (malloc/free), она элементарно заработает под ARC. А если программа была под ARC, то она без изменений будет работать под GC (кроме расчета на немедленную деструкцию).
Надежность
Oxygene
Это в принципе старый знакомый Object Pascal, но на стероидах. Версия Oxygene for .NET также продавалась как Delphi Prism.
Выглядит безопаснее за счет:
- GC
- поддержки контроля nullable
- наличия контрактов и инвариантов
- контроля регистра идентификаторов
- возможности контроля отступов пар begin/end (непонятно как работает)
- locked/lazy потокобезопасных методов класса
- наличия using, в смысле RAII
Минусы. GC же имеет и обратную сторону — временную недетерминированность, избыточное потребление памяти. Также в минусы можно записать, что масштаб фирмы и сообщества — небольшой и потому ошибок реализации вероятно больше, чем для техномонстров. Документация слабая, а родной фреймворк минимален, что может потянуть кучу “велосипедов” в проекты, если ваш таргет не .NET или JRE.
С технической же стороны, в нем добавлено сахара относительно Delphi
- async/await/future, await — пока только для .NET
- LINQ
- generic/dynamic типы (отличаются от Delphi)
- tuples
- sequence/yield
- string interpolation
Интерлюдия. C#
Ранее был отсеен из-за ограниченной кроссплатформенности, но с выходом .NET Core 3, и появлением нативного компилятора в том числе для Linux, стало чуть лучше. Дополнительно, язык наравне поддерживается в RemObjects с произвольным выбором таргетов.
Собственно, с надежностью у C# более-менее все в порядке. Красоту портит GC, не очень удобная работа с nullable, что приводит к регулярным NPE, возможность получить исключение из невинно выглядящей функции, и проблемы при неудачном использовании LINQ.
Вследствии GC и ресурсопотребления, не очень то подходит для Embedded. Тем более, что для ембеддед только интерпретатор без JIT .NET Micro Framework заброшен с 2015г, хотя и получил развитие как TinyCLR
Теперь о Swift
В общем и целом, C-подобный компилируемый в машинный код язык, один из новейших и созданный с учетом истории предшественников. Сделан Apple для себя, официальный компилятор есть еще под Ubuntu, переведен в Open Source.
Возможности на уровне, надежности уделено приличное внимание. Могу придраться к возможности изменения константных классов и возможности спутать ссылочный класс со структурой — значением, впрочем тут как в C#.
Управление памятью — в оригинале ARC, а у RemObjects для платформ, отличных от Apple — GC.
Тяжеловат для Embedded, нет реалтаймовых способностей.
Остальное, что бы я хотел видеть — почти все есть.Контрактов нет (в RemObjects есть нестандартные расширения), но возможна какая то их эмуляция через Property Observers или появившиеся в 5.2 @propertyWrapper (в RemObjects еще не реализовано)
Стоит отметить тьюплы, именование параметров функций,
array.insert(x, at: i)
явный unwrap для Optional, да и в целом продуманная с ним работа.
Существенным нюансом я бы назвал эволюцию языка. Многие примеры из официального учебника (5.2) просто не работают на том же Repl.it (5.0), не говоря уж о RemObjects, которые непонятно какой точно версии соответствуют.
Реализация в RemObjects отличается от эталонной — Array тут ссылочный, а String иммутабельный, интерфейсы причем тоже отличаются от описанных в учебнике, да и интерфейсы библиотек мало того что свои, так и немного варьируются между платформами. Что привносит некоторое разнообразие в скучном процессе кодописания =)
Итоговая сводная обновленная таблица надежности
Собственно, напоминаю, что это только лишь поверхностный по справочным данным, и все профи, которые пишут на этих языках, приглашаются к жестким правкам.
Качество компиляции
Попробуем сделать программу на Swift, аналогичную Расстоянию Левенштейна в статье 0xd34df00d и сравнить ее с программами оттуда же.
/* Нахождение расстояния Левенштейна
Версия для Windows.Native, Elements.Island
*/
class Program {
public static func LevDist(_ s1: [UInt8], _ s2: [UInt8]) -> Int32! {
let m = s1.count
let n = s2.count
// Edge cases.
if m == 0 {
return n
} else {
if n == 0 {
return m
}
}
var range = Range(0 ... (n + 1))
var v0: Swift.Array<Int64> = range.GetSequence() //17ms
// var v1 = v0; // in Swift must copy, but RO make a ref
var v1 = Swift.Array<Int64>(v0);
var i = 0
while i < m {
v1[0] = i + 1
var j = 0
while j < n {
let substCost = (s1[i] == s2[j] ? v0[j] : v0[j] + 1)
let delCost = v0[j + 1] + 1
let insCost = v1[j] + 1
v1[j + 1] = substCost < delCost ? substCost : delCost
if insCost < v1[j + 1] {
v1[j + 1] = insCost
}
j += 1
}
let temp = v0; v0 = v1; v1 = temp // copy refs
i += 1
}
return v0[n]
}
}
var b1 = [UInt8](repeating: 61 as! UInt8, count: 20000)
var b2: [UInt8] = b1
var b3 = Swift.Array<UInt8>(repeating: UInt8(63), count: 20000)
print("Start Distance");
var execTimer: StopWatch! = StopWatch()
execTimer.Start()
print(Program.LevDist(b1, b2))
print(Program.LevDist(b1, b3))
execTimer.Stop()
var execTime: Float64 = execTimer.ElapsedMilliseconds / 1000.0 / 10
print("(execTime) s")
return 0
Остальные с минимальными изменениями, хотя конечно же наиболее жесткие огрехи я подровнял, на гитхабе. Кто не боится и лень качать среду и компилировать, там же есть и бинарники, при желании можно позапускать относительно безопасные JVM и NET варианты, или же в песочнице.
Изменения касаются правок для RO, выноса Encoding.UTF8.GetBytes из замерной части, ну и выяснилось, что в паскалевской версии формирование строк в виде
for i := 1 to 20000 do s1 := s1 + 'a';
может занимать до 4х минут (вне замерной части).
Таблица результатов (отнормирована по Java, как универсальной)
Язык | Платформа | Компилятор | Время, с | Норма | Параметры |
---|---|---|---|---|---|
C# | NET4.7 | NET4.7.3062 | 13,075 | 445% | -o+ |
C# | NET Core 3.1 | Elements 10.0 | 11,327 | 385% | release, >dotnet cs.dll |
C# | Win x64 | Elements 10.0 | 6,312 | 215% | release |
Java | JVM | JDK 1.8.0.242 | 2,941 | 100% | -g:none $java LevDist |
Java | JVM | Elements 10.0 | 2,851 | 97% | release $java -jar java8.jar |
Oxygene | NET4.7 | Elements 10.0 | 22,079 | 751% | release, range checking off |
Oxygene | Win x64 | Elements 10.0 | 9,421 | 320% | release, range checking off |
Swift | JVM | Elements 10.0 | 23,733 | 807% | release $java -jar swiftjvm.jar |
Swift | Win x64 | Elements 10.0 | 131,400 | 4468% | release |
Go (Beta) | Win x64 | Elements 10.0 | 89,243 | 3034% | release |
Было бы интересно еще протестировать WASM, но я упустил этот момент.
Измерения проводились в виртуальной машине с Win7, примерно втрое замедляющей эту задачу относительно реального моего железа, из 5 замеров брались средние 3.
Вывод пока один — жизнь на Swift’e возможна и за пределами яблочной экосистемы. Но пока небыстрая. Гипотеза, что есть проблема с Win7, поскольку для нативной компиляции используется Win10 SDK, не подтвердилась — на Server 2016 1607 времена примерно те же.
[1] Надежное программирование в разрезе языков — нубообзор. Часть 1
[2] Надежное программирование в разрезе языков. Часть 2 — Претенденты
Автор: Siemargl