В серии про надежное программирование [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 и сравнить ее с программами оттуда же.

Swift
/*  Нахождение расстояния Левенштейна
	Версия для 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 — Претенденты