Добавление "универсального" префикса типа для переменных
Было предложено следующее изменение языка: при отсутствии префикса типа для переменной / массива, считать это “универсальным” префиксом и записывать / читать значение “как есть”.
Сейчас, если в переменной $X[0] записана строка, то обращение X[0] вернет 0, т.к. тип не соответствует ожидаемому и возвращается значение по умолчанию (ячейка массива как бы не инициализирована).
Если добавить “универсальный” префикс, то X[0] вернет строку - фактическое значение в ячейке.
Аналогично с присваиванием - станет возможным делать X[0]="asdasdas" - будет присвоена строка.
Это изменение позволит упростить некоторые сценарии, когда тип значения неважен - нужно просто его вывести / прочитать / передать куда-то еще.
Обратная совместимость не нарушается, если для неинициализированных переменных возвращать 0 (по умолчанию универсальный префикс - число).
Главный минус этого “универсального” префикса - будет сложнее понимать какой тип данных указан в переменной / что ожидает игра - нужно проверять фактические присваивания. В этом случае есть риск того, что авторы начнут повсюду использовать универсальный префикс и разбираться в коде станет сложнее.
Из компромиссов:
-
разрешить чтение / запись значения “как есть” только для
ARRITEM/SETVAR. -
добавить новый, более явный префикс - например “~”:
~X[0]/~X(мне не очень нравится этот вариант, честно говоря).
Хочется обсудить и услышать разные мнения - нравится / не нравится, нужно / не нужно / как лучше это сделать.
PS. Явные префиксы $ / # / % в любом случае останутся.
- mass - универсальный.
- $mass - текстовый.
- ~mass - числовой.
Один вопрос, а зачем тогда вообще пользоваться префиксами? Я это уже где-то встречал (вырезка из справки):
SET и LET. … это никак не влияет на работу переменных.
p.s. Прикрутите Qt к классику - будет ОГОНЬ!
Уже сейчас числовой префикс - это # (работает в последних версиях).
Пример: #a=44 & pl #a
Префиксы удобны для явного указания какой тип мы хотим записать или прочитать - в противном случае, при чтении кода, нужно было бы искать какое значение присваивалось переменной. Но можно это оставить на усмотрение автора кода - если захотят, то смогут использовать явные префиксы.
PS. Да, в планах сделать новый классик (совместимый с текущим). Вероятно, это будет на Qt.
Не знал про #, тогда добавлять новый, более явный префикс и не нужно вовсе, уже все есть.
Универсальность снизит порог вхождения для новых авторов, а ошибки были, есть и будут. Идея хорошая!
Введение универсального префикса имеет как свои плюсы, так и свои минусы.
Изложу своё мнение, как человек, написавший много “плохого” кода и на qsp, и на python и на js, и немного касавшийся других языков программирования, где используется строгая типизация.
многа букав
Текущее положение дел
Прежде всего надо отметить, что в qsp сейчас не динамическая, но и не строгая типизация.
Префикс типа путает авторов, пришедших из других языков, тем, что интуитивно не понятен его механизм работы. Мы уже обсуждали с некоторыми авторами, как относиться к префиксу типа, как его описывать. Это оператор или это часть имени переменной?
Не могу знать точно, как это работает в других языках с динамическими типами, но в python переменные — это ярлыки, ссылки на объекты. И когда мы обращаемся к переменной, на самом деле под капотом python происходит сложная магия, которая заставляет объект возвращать какое-либо значение в зависимости от ситуации. Так print(5) и print('5') не отличаются ничем в плане вывода, но обработка происходит по-разному, поскольку 5 — это объект класса int, а '5' — это объект класса str. Но, это встроенные функции и операторы знают, куда им тыкнуться, к какому магическому методу обратиться, а вот когда мы пишем собственный метод, принимающий не строго типизированные данные, мы начинаем городить лапшу из instanceof.
В строго типизированных языках сам синтаксис приучает объявлять необходимые для работы программы типы. Такие языки и такая типизация нужны, чтобы заранее выделить память на компьютере под значение переменной. Это заставляет думать над структурой собственных функций, а то и всей программы, прорабатывать места, где можно получить данные с заранее неизвестным типом, или приводить данные к одному формату при подготовке к обработке, и т.п. То есть делать код более строгим и структурированным ещё на этапе написания.
Обычно, даже в языках с динамической типизацией, автор задумывается о необходимости строгой типизации. Так появился typescript, так появились typing и другие способы прописывать строгие типы в python. Это просто надстройки для IDE и линтеров, позволяющие строго отслеживать, куда какие данные приходят. В то же время в языках со статической типизацией появляется необходимость в обработке разнотипных данных, отсюда перегрузка методов, отсюда всякие дженерики, и иные способы и ухищрения, чтобы писать поменьше кода, но обрабатывать всё.
И вот мы возвращаемся к qsp. До 5.8.0 у нас фактически $arr и arr были разными переменными. И это было интуитивно понятно. Они и записываются по-разному и содержат различные типы данных. Т.е. типизация у нас, если так подумать, была строгая. Однако проблема заключалась в том, что по сути это всё равно был один массив, хранивший параллельно два разнотипных значения. Это порождало неоднозначности. Например, при чтении массива. Как понять, что ячейка содержит оба значения, или какое-то одно, если это значение по умолчанию? Как поведёт себя arrpos, если мы ищем пустую строку, но в массив её не вносили, а вот числовые значения вносили? Это уже требует изрядного терпения и внимания.
И отсюда мы шагнули в динамическую типизацию. Теперь $arr и arr — это однозначно одна и та же переменная. Больше нет путаницы при чтении массива, но теперь смысл префиксов стал неясен.
Мы указываем тип значения, которое помещаем в переменную, используя префикс. Но, если мы попытаемся считать из неё значение другого типа, это не вызовет ошибки, мы просто считаем значение по умолчанию. Т.е. префикс сейчас — это что-то вроде оператора. Причём это два разных оператора. В момент объявления переменной это оператор проверки типа значения, которое мы вносим в переменную, а в момент извлечения значения — это оператор приведения типа. Да, он работает специфическим образом, но трактовать его иначе сложно.
Именно на этом этапе мы и остановились сейчас. У нас частично строгая, частично динамическая типизация. И надо понять не только, двигаться ли нам дальше, но и — в какую сторону.
Плюсы и минусы
Сразу давайте условимся, что универсальный префикс — это отсутствие префикса. Тут надо подумать и учесть возможные подводные камни такого подхода.
Плюсы универсального префикса:
- Универсальный префикс сделает код более читаемым.
- Снизится порог входа для авторов, которых может быть пугают префиксы.
-
Больше не нужно будет знать, какого типа данные лежат в массиве для вывода массива на экран (сейчас приходится использовать
arrtypeиarritem). Этот пример можно экстраполировать на любой код, требующий вывода данных, типы которых заранее неизвестны. - Легче писать локации-функции, в которых могут быть заранее неизвестны типы аргументов. Например, локации-функции “обёртки”, или локации-функции, которые обрабатывают коллбэки в виде динамического кода.
- типизация будет полностью динамической (что записали в переменную, то и получили без префиксов).
Минусы универсального префикса:
-
Что должна вернуть неинициализированная переменная? Кажется очевидным
0для сохранения совместимости со старыми играми, но всё становится не так очевидно, когда мы сталкиваемся сtext += 'str'для неинициализированной переменной. - Авторы не будут вообще обращать внимания на типы. Это приведёт к тому, что средне статистический код куспа станет менее строгим. Это в свою очередь приведёт к тому, что авторы будут допускать больше ошибок, которые кусп легко прощает, но которые выльются в тонны игровых багов.
Есть вариант не вводить универсальный префикс, а наоборот сделать типизацию более строгой.
Я про сообщение о “несоответствии типов данных” при попытке прочитать, например, из текстовой переменной число, или кортеж, или наоборот. Более того. Если переменная была инициализирована и не “убита”, тип её поменять нельзя.
Из плюсов такого подхода: это более строгий код, авторы будут думать над ним, будут следить за типами, структурировать игру; проще делать статический анализ в различных IDE.
Из минусов: раздражение от того, что ошибка “несоответствие типа данных” всплывает только во время интерпретации; повышение порога входа для авторов, которые переходят из других языков, потому что они не понимают, зачем указывать тип переменной везде, где она используется, а не только при объявлении.
Третий вариант
По сути это голос за переход к полностью динамической типизации и универсальному префиксу, но с более строгой обработкой префиксов типа.
Ввиду того, что у нас отсутствуют IDE, которые на лету бы отлавливали ошибки, когда автор пытается работать не с тем типом данных, которым инициализировал переменную, префиксы типов — наиболее удобный способ следить за тем, с какими данными ты работаешь. Не стоит забывать об опыте “Венгерской” нотации. Копий сломано много, но подход имеет место быть. Когда я сел за пайтон после куспа, мне жуть как не хватало префиксов типа при работе в Sublime Text, а variable: str = 'string' в таком простом редакторе — это бессмысленная и лишь загромождающая код запись. У меня до сих пор осталась привычка делать не files, а files_list, хотя я в ST сейчас на пайтоне не пишу.
Третий вариант предполагает:
-
Для новичков — универсальный префикс, который без инициализации возвращает
0и по сути очень лоялен к разного рода проверкам. - Для старичков, и для тех, кому нужно наглядно видеть типы данных — префиксы типов, но с оговоркой: при попытке получить данные иного типа из переменной/ячейки массива нужна ошибка о несоответствии типов данных.
Третий вариант вызывает много вопросов по совместимости, и кмк усложняет работу самому плееру. Префикс типа по прежнему интуитивно не понятен, что это: оператор, часть имени переменной, или что, — но поскольку до его использования будут доходить немногие, это особого значения не имеет.
Несовместимости
Я обозначу релиз с универсальным префиксом, как 5.10.0 для краткости.
В старой игре такой код:
number = 'string'
Сейчас мы видим ошибку “несоответствие типов данных”. В 5.10.0 такой ошибки не будет.
Если игру можно продолжать, при попытке считать number сейчас мы увидим 0. В 5.10.0: 'string'.
При попытке считать именно строку ($number) сейчас мы увидим пустую строку, в 5.10.0 'string'. Т.е. потенциально универсальный префикс некоторые игры даже починит.
Если не рассматриваем реализацию третьего варианта, то сейчас %number вернёт пустой кортеж, и в 5.10.0 — пустой кортеж.
Третий вариант должен требовать явной инициализации переменной с типом кортеж, а поскольку в ней лежит строка, появится ошибка “несоответствие типов данных”.
Попытка считать данные из неинициализированной переменной.
Сейчас возвращает значение по умолчанию для того типа, который пытаемся считать, причём number возвращает 0.
В 5.10.0 то же самое.
В третьем варианте только number возвращает 0. Остальные префиксы вызывают ошибку.
Для третьего варианта: попытка считать данные по универсальному префиксу не вызывает ошибку, а работает, как для 5.10.0. Т.е. если хоть какие-то данные в переменной есть, они считываются. (это так, уточнение на случай, если я слишком сумбурно обозначил третий вариант).
Как должны вести себя такие конструкции:
number += 'str'
Для неинициализированных переменных можно сразу проводить конкатенацию с пустой строкой, но если переменная инициализирована другим типом данных?
Сейчас это вызывает ошибку о несоответствии типов данных из-за того, что number — это числовой тип.
Промежуточные выводы
К третьему варианту мы пока не готовы, потому что умрёт совместимость со старыми играми. Да и дальше скорее всего мы будем к нему не готовы, если только все игры не начнут делать исключительно с универсальным префиксом, а с префиксами конкретных типов только с учётом, что когда-то однажды префиксы типа будут очень строго следить за типом данных.
Введение универсального префикса может сломать приведение типов в выражениях, что тоже поломает совместимость. Над этим надо серьёзно подумать и накидать как можно больше примеров, когда это может потенциально вызвать несовместимость.
Однако. Идти в сторону динамической типизации нужно, но не торопясь. Обдумывая каждый шаг.
P.S.: прошу прощения за некоторую сумбурность и повторы мыслей.
Upd: ещё для третьего варианта несовместимость. Если на локации есть result, потом присвоение $result это по логике должно вызывать ошибку. Что приводит к загромождению кода killvar’ами всякими. Третий вариант мне теперь вообще не нравится.
Надо проверки типов перекладывать на IDE.
Для операций вида a += 'test' или a = b + 'test' можно ввести правило “неинициализированная переменная при операции со значением какого-то типа приводится к значению по умолчанию для этого типа”. Тогда для кода выше в a будут строки “test”, если переменные не были инициализированы.
Aleks Versus:
Надо проверки типов перекладывать на IDE.
Да, я как раз начал заниматься разработкой расширения для vscode.