Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
С Python мне пришлось работать от безнадеги - ML, нейросетки, скриптинг, то-сё сподручнее было именно на нем. Но время идет и тревога за скорость своего кода толкает к чему то быстрому и более надежному.
Задача портировать GUI framework наболела, потому что мой универсальный Unigui работал только из Python и универсальным был только в теории. В нем был наработан предельно лаконичный API, который должен был быть сохранен и в Go варианте. Кроме того масса автоматизма по генерации нужных данных портированию не подлежала, т.к. в Go отсутствует управление порядком компиляции, препроцессор,метапрограммирование, что гарантировало непростой спартанский трип.
Первой сложностью, в которую я наступил, была невозможность напрямую присваивать promouted(вложенные) поля в инициализаторе структур и только в нем. Если структура имеет безымянную вложенную структуру, то к ее полям можно обращаться из корня внешней везде, кроме инициализатора. Из-за чего, в том числе, вся затея с вложенными полями как с заменой иерархии стала и сомнительной, и громоздкой в использовании. Требовать от юзера либы писать длинные вложенные с ненужными типами инициализаторы было бы верхом неприличия. Все GUI структуры стали абсолютно плоские, независимые, хотя в Python есть четкая иерархия.
Словоблудие, которые Go требовал при сборке объектов, все равно оставалось. Пользователю библиотеки нужен краткий понятный вызов, а тут я должен заставить его помнить поля объекта и использовать в инициализаторе и каким-то образом указать, что некоторые должны быть обязательно заполнены, а дефолтные параметры низзя,они, как оказалось, “следствие плохого дизайна API”. Золотые слова, как обычно, с реальностью не коррелирующие. Было решено спрятать это с глаз и дать юзеру набор хэлперов с теми же именами, что должны быть у типов, которые будут делать всю нудную работу и возвращать сразу указатель. Что пользователю легко забыть хэлпер сделает сам. Типы объектов стали выглядеть как Type_ a хелпер Type(…) возвращает *Type_. По сути, это конструкторы из ООП, чтобы скрыть неприглядные моему и юзера либы взору кишки Go кода.
Сериализация Go в Json не осталась в стороне и взбодрила порцией эзотерики в своих деталях. nil работает как пустой массив в вызовах функций слайсов (массивы переменной длины), однако при сериализации поля слайса с nil он продолжает быть nil a не как во всех других языках/библиотеках [ ] . То, что тип переменной слайс - по фигу. Оk, Google, возьмем стороннюю либу с фиксом на это дело или будем в хелперах проверять, если массив == nil, то надо ему написать = make(0, []Any). Если поле структуры имеет неинициализированный указатель на функцию, то напрямую он == nil, а если запросить его значение через reflect то он != nil. Однако если дописать бессмысленную на первый взгляд reflect.ValueOf(ptr).IsNil() то, о чудо, это true! Гениально, чо..
Добить меня они решили отсутствием стандартной функции hash. Так мне тогда показалось. Это ж надо было умудриться. Проверил на всякий случай список арифметических операторов. Вроде все есть. Выдохнул и стал пробираться дальше.
Почему можно выводить и не писать тип для простой переменной можно, но нельзя для массива или мапы?? Ведь итоговый тип либо равен типу первой переменной, если он совпадает с остальными в списке, либо Any - interface{}. Всё! А потому явно писать тип для мапов и слайсов, когда он 100% очевиден - это проявление внимания к работе компилятора. “Вам мелочь, а ему приятно!”
Про ненормальное определение видимости переменных и функций через регистр первой буквы. Наверное, это не новость, однако же и ломка сознания (известные мне языки такую дичь не используют) и путаница, где тип, а где объект. Если кажется что к этому можно привыкнуть и перетерпеть, то дичь будет лезть везде, где пролезет. Вот хотим мы сделать сериализацию готового протокола, а имена в нижнем регистре использовать не можем (доступа не будет). Любезный Google предлагает нам костыль с блестками в виде добавок к описанию поля типа
MyField int json:"myfield"
и вроде бы ничего, с JSON мы выпутываемся. Подумаешь - пробежаться по сотне-другой полей и дописать эту фуфу. А если сериализация в другой протокол? Тогда, горемычные, - ваши проблемы, они и так вон чего тебе аж, а вам все мало. Пиндюрь к каждой структуре свою функцию кодирования и радуйся что ээ.. да просто радуйся!
Тот же гугловый Dart имеет симпатичную декларацию видимости через _ впереди для ее ограничения. К слову сказать, ВСЕ печальки Go в Dart-е отсутствуют, и наоборот. Т. е. создается ощущение, что кривозубые решения в дизайне языков в Google решили между ними поделить, и ни один такой “прикол” не вошел одновременно в оба языка. Кто это, интересно, у них это раздает, и следит, чтоб всем поровну?
Отсутствием на данный момент дженериков, функторов map/filter/.. , коротких лямбд и даже таких элементарных операций с массивами как RemoveAtIndex, возмущались и до меня, но с добавкой дженериков в 1.17 часть этого уйдет, но я готов поставить ящик мороженого, что в них докинут новой эпидерсии, чтоб не сильно радовались.
Зачем вам интерполяции строк? Забейте! Вместо этого предлагается использовать разлапистый fmt.Sprintf. Что за необходимость писать допотопный С-код в современном языке - тайна великая, а их объяснение, что это сложно компилятору - “муть и компот”.
Но есть же и приятные моменты.
И главный - это скорость. Ощущение, когда пересядешь с допотопного ноута на фаршированный десктоп. Как то сразу хочется понять и простить, остаться, задумываешься как ML-ить на Go…
Конкурентность/параллелизм. Почувствовав разницу, понимаешь, что использование для нагруженных задач Python-a вместо Go - нехорошо ни разу. Модель гоуроутин - ясная, простая, минимум усилий, максимум выхлопа. Python здесь сливает как бомж-алкоголик директору винзавода.
Сборка/компиляция - здесь в плюс Go. Импорт либ из github (жирный лайк), мгновенная инкрементная компиляция это не кот накашлял. Правда, последнее уже было в Visual C++ ~20 лет назад, Edit & Continue называлось. Оно, Edit & Continue для Go в моем VS Code не работает, хотя при каждом редактировании при запущенной проге пугает, что изменения якобы внесены. Ни-фи-га. Ну то такое ..
Объем кода. Примерно одинаково. Что весьма хорошо для Go. Плохо, что добиться этого сразу нельзя. Медитировать, переписывать, хэлперы, алиасы - тогда можно.
-Дизайн кода. Довольно просто подсчитать, что если бы Go поддерживал элементарное наследование, то кода в моей либе стало бы на 20% меньше. И работала бы она минимум вдвое быстрее за счет исключения 70% reflect кода. И написал бы вдвое быстрее. Так что лично мне понятно, что вся эта типо инкапсулирующая модель создает больше проблем чем пользы. Причем существенно.
Сравнение кода. Для создания вот этого экрана в браузере (сервера по умолчанию вешаются на 8000 порт) код на Python и Go.
Go
package main
import . "github.com/Claus1/unigui-go"
func screenTest(user* User)* Screen_{
table := Table("Videos",0, nil, []string{"Video", "Duration", "Links", "Mine"},
SeqSeq(Seq("opt_sync1_3_0.mp4", "30 seconds", "@Refer to signal1", true),
Seq("opt_sync1_3_0.mp4", "37 seconds", "@Refer to signal8", false)))
cleanButton := Button("Clean table", nil, "")
selector := Select("Select", "All", nil, []string{"All","Based","Group"})
block := Block("X Block", Seq(cleanButton, selector), table)
block.Icon = "api"
return Screen(block)
}
func main(){
//register screens
Register(screenTest, "Main", 0, "insights")
Start()
}
Python
from unigui import *
name = "Main" #name of screen to show
icon = 'blur_linear' #MD icon of screen to show
order = 0 #order in the program menu
table = Table('Videos', 0, headers = ['Video', 'Duration', 'Links', 'Mine'],rows = [
['opt_sync1_3_0.mp4', '30 seconds', '@Refer to signal1', True],
['opt_sync1_3_0.mp4', '37 seconds', '@Refer to signal8', False]
])
block = Block('X Block',
[
Button('Clean table'),
Select('Select', value='All', options=['All','Based','Group'])
], table, icon = 'api')
blocks = [block] #what to show on the screen
start('Test app')
Буду ли использовать Go дальше? Куда ж я денусь с подводной лодки.. и сильно хочу, чтоб Crystal, Nim, и прочие простые, развитые языки ворвались в продакшен и дали Go пендаля. Пока, к сожалению, альтернативы ему в нише легкой продакшен разработки производительного ПО не наблюдаю. Peace!
Ссылки для любознательных:
https://github.com/Claus1/unigui-go Go
https://github.com/Claus1/unigui Python