Порт GUI фреймворка с Python на Go. Анализ граблей и плюшек

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру 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 

Источник: https://habr.com/ru/post/544300/


Интересные статьи

Интересные статьи

Всем привет! Сегодня мы будем пытаться собрать Python 1.0.1 (1994 год) на современном железе при помощи современного компилятора. Даже если Вы, как Python разработчик, никогда не компилир...
Анализ безопасности хранения и хеширования паролей при помощи алгоритма MD5 С появлением компьютерных технологий стало более продуктивным хранить информацию в базах данны...
Несколько лет назад компания Veeam открыла R&D центр в Праге. Изначально у нас был небольшой офис примерно на 40 человек, но компания активно растет, и сейчас, в новом просторном офисе Rust...
Мы привычно пользуемся интернет-поиском, общаемся с чат-ботами, читаем документы на любых языках благодаря переводчикам. Приказать роботу-пылесосу начать уборку при помощи голоса? Ничего особенно...
Если бы я давал Нобелевскую премию Жану Тиролю, я бы дал за его теоретико-игровой анализ репутации, или хотя бы включил в формулировку. Мне кажется это тот случай, когда наша интуиция хорошо ...