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

С 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. Что за необходимость писать допотопный С-код в современном языке — тайна великая, а их объяснение, что это сложно компилятору — “муть и компот”.

Но есть же и приятные моменты.

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 

@george3
26.02.2021 10:36 UTC
Первоисточник

Комментарии

@Siemargl
26.02.2021 09:13 UTC
0
Кроме того масса автоматизма по генерации нужных данных портированию не подлежала, т.к. в Go отсутствует управление порядком компиляции, препроцессор, метапрограммирование
А как же go generate?
@george3
26.02.2021 11:40 UTC
+1
Она ж не встроена в язык. Внешние средства не в счет, а то иначе все языки поддерживают метапрограммирование и много чего еще.
26.02.2021 11:49 UTC
0
Но она встроена в стандартный тулчейн.

Если что, в С/С++ зачастую препроцессор тоже внешним модулем.
26.02.2021 14:09 UTC
0
Я ортодокс: смотрю что можно сделать изнутри проги. Оттуда генерить код нормальным способом как могу в динамических языках и в Nim, Crystal, Scala — не могу. Просить юзера изучать/использовать тулчейны чтобы он смог использовать мою либу не считаю разумным. но спасибо за наводку.
@basiliscos
26.02.2021 09:18 UTC
0

Не совсем понятно — это нативный GUI или веб?

@george3
26.02.2021 11:35 UTC
0
Web. Есть и нативный web, когда нативный клиент коннектится к серверу, без браузера.
@george3
26.02.2021 11:52 UTC
0
Ссылки в конце статьи это ссылки на репозитарии Unigui Python и Go, на всяк случай.
@
26.02.2021 16:35 UTC
0
НЛО прилетело и опубликовало эту надпись здесь
@george3
26.02.2021 18:27 UTC
0
Примерно так, чтобы еще возращал указатель, иначе никак. + без лишних имен в конструкторе.