Pull to refresh

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

Reading time5 min
Views3.7K

С 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!

Ссылки для любознательных:

Go 

Python 

Tags:
Hubs:
0
Comments9

Articles

Change theme settings