В серии про надежное программирование [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.
А еще в дополнение к обсуждениям о проблеме интероперабельности разных сред исполнения — нативных, JVM, .NET, GC, ARC она оказалась просто невероятной, причем в обоих смыслах — так как при обсуждениях пришли к тому, что нормальная ИО невозможна.
Это либо независимая среда разработки с отладчиком, в том числе и удаленным, и автодополнением кода для Windows или macOS, либо же интегрируется с Visual Studio, а для Cocoa обязателен XCode. Для Swift’a она бесплатна, для остального стоит от 199$ за версию для личных проектов, до 799$ за коммерческую лицензию для всех языков. За работу с клиент — серверными СУБД придется доплатить уже существенно.
Water — так называется версия для Windows, довольно аскетична, визуального редактора форм в ней нет, но симпатизирует строгостью, да и установщик занимает всего 700МБ, не включая конечно же JRE, .NET, Android NDK итп.
Ругать её, или сильно хвалить я не буду, свои функции выполняет, не падала ни разу (но память течет).
Бонусом к IDE идет много:
Конечно, есть небольшой подвох для тех, кто думает что (с) можно просто взять и перенести любую программу на другую платформу.
ИО здесь заключается в том, что программа умеет использовать только лишь возможности своей RTL _и_ целевой платформы для всех языков и их комбинаций. То есть для цели .NET можно использовать WPF, но не rt.jar, а для Native — только WinAPI или GTK соответственно. Но базовая библиотека, написанная на Oxygene, доступна везде, как и прослойка миграции с Delphi — кстати они доступны на гитхабе.
Проект же может включать в себя подпроекты на разных языках, поддерживаемых RO. И с импортом внешних библиотек тоже есть проработанные варианты.
Управление памятью будет использоваться от целевой платформы, например ARC для Cocoa. В детали я не вдавался, для Native по умолчанию используется GC, хотя есть выбор — проверено, есть в моем проекте на github.
Добавлю одно наблюдение. Если программа писалась под ручное управление памятью (malloc/free), она элементарно заработает под ARC. А если программа была под ARC, то она без изменений будет работать под GC (кроме расчета на немедленную деструкцию).
Это в принципе старый знакомый Object Pascal, но на стероидах. Версия Oxygene for .NET также продавалась как Delphi Prism.
Выглядит безопаснее за счет:
Минусы. GC же имеет и обратную сторону — временную недетерминированность, избыточное потребление памяти. Также в минусы можно записать, что масштаб фирмы и сообщества — небольшой и потому ошибок реализациивероятно больше, чем для техномонстров. Документация слабая, а родной фреймворк минимален, что может потянуть кучу “велосипедов” в проекты, если ваш таргет не .NET или JRE.
С технической же стороны, в нем добавлено сахара относительно Delphi:
Ранее был отсеен из-за ограниченной кроссплатформенности, но с выходом .NET Core 3, и появлением нативного компилятора в том числе для Linux, стало чуть лучше. Дополнительно, язык наравне поддерживается в RemObjects с произвольным выбором таргетов.
Собственно, с надежностью у C# более-менее все в порядке. Красоту портит GC, не очень удобная работа с nullable, что приводит к регулярным NPE, возможность получить исключение из невинно выглядящей функции, и проблемы при неудачном использовании LINQ.
Вследствии GC и ресурсопотребления, не очень то подходит для Embedded. Тем более, что для ембеддед только интерпретатор без JIT .NET Micro Framework заброшен с 2015г, хотя и получил развитие как TinyCLR
В общем и целом, C-подобный компилируемый в машинный код язык, один из новейших и созданный с учетом истории предшественников. Сделан Apple для себя, официальный компилятор есть еще под Ubuntu, переведен в Open Source.
Возможности на уровне, надежности уделено приличное внимание. Могу придраться к возможности изменения константных классов и возможности спутать ссылочный класс со структурой — значением, впрочем тут как в C#.
Управление памятью — в оригинале ARC, а у RemObjects для платформ, отличных от Apple — GC.
Тяжеловат для Embedded, нет реалтаймовых способностей.
Остальное, что бы я хотел видеть — почти все есть.Контрактов нет (в RemObjects есть нестандартные расширения), но возможна какая то их эмуляция через Property Observers или появившиеся в 5.2 @propertyWrapper (в RemObjects еще не реализовано)
Стоит отметить тьюплы, именование параметров функций,
Существенным нюансом я бы назвал эволюцию языка. Многие примеры из официального учебника (5.2) просто не работают на том же Repl.it (5.0), не говоря уж о RemObjects, которые непонятно какой точно версии соответствуют.
Реализация в RemObjects отличается от эталонной — Array тут ссылочный, а String иммутабельный, интерфейсы причем тоже отличаются от описанных в учебнике, да и интерфейсы библиотек мало того что свои, так и немного варьируются между платформами. Что привносит некоторое разнообразие в скучном процессе кодописания =)
Итоговая сводная обновленная таблица надежности
Собственно, напоминаю, что это только лишь поверхностный по справочным данным, и все профи, которые пишут на этих языках, приглашаются к жестким правкам.
Попробуем сделать программу на Swift, аналогичную Расстоянию Левенштейна в статье 0xd34df00d и сравнить ее с программами оттуда же.
Остальные с минимальными изменениями, хотя конечно же наиболее жесткие огрехи я подровнял, на гитхабе. Кто не боится и лень качать среду и компилировать, там же есть и бинарники, при желании можно позапускать относительно безопасные JVM и NET варианты, или же в песочнице.
Изменения касаются правок для RO, выноса Encoding.UTF8.GetBytes из замерной части, ну и выяснилось, что в паскалевской версии формирование строк в виде
Таблица результатов (отнормирована по Java, как универсальной)
Было бы интересно еще протестировать WASM, но я упустил этот момент.
Измерения проводились в виртуальной машине с Win7, примерно втрое замедляющей эту задачу относительно реального моего железа, из 5 замеров брались средние 3.
Вывод пока один — жизнь на Swift’e возможна и за пределами яблочной экосистемы. Но пока небыстрая. Гипотеза, что есть проблема с Win7, поскольку для нативной компиляции используется Win10 SDK, не подтвердилась — на Server 2016 1607 времена примерно те же.
[1] Надежное программирование в разрезе языков — нубообзор. Часть 1
[2] Надежное программирование в разрезе языков. Часть 2 — Претенденты
Оказалось, у неё Универсальный Компилятор. Умеет компилировать программы на 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 же имеет и обратную сторону — временную недетерминированность, избыточное потребление памяти. Также в минусы можно записать, что масштаб фирмы и сообщества — небольшой и потому ошибок реализации
С технической же стороны, в нем добавлено сахара относительно 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 |
Измерения проводились в виртуальной машине с Win7, примерно втрое замедляющей эту задачу относительно реального моего железа, из 5 замеров брались средние 3.
Вывод пока один — жизнь на Swift’e возможна и за пределами яблочной экосистемы. Но пока небыстрая. Гипотеза, что есть проблема с Win7, поскольку для нативной компиляции используется Win10 SDK, не подтвердилась — на Server 2016 1607 времена примерно те же.
[1] Надежное программирование в разрезе языков — нубообзор. Часть 1
[2] Надежное программирование в разрезе языков. Часть 2 — Претенденты
NeoCode
А Swift для Windows как там реализован?
Какие вообще есть реализации?
Есть ли реализации через MinGW?
Siemargl Автор
Там, кроме Go, везде собственный Elements Compiler, есть библиотека RemObjects.Elements.LLVM.dll, но где она используется, я через ProcessExplorer не увидел.
Второй вопрос не понят
NeoCode
Второй вопрос — возможно кто-то знает какие нибудь реализации swift для windows кроме RemObjects. Я предположил что может быть существует реализация типа mingw, возможно были попытки портировать с ubuntu?