Добавление "универсального" префикса типа для переменных
Было предложено следующее изменение языка: при отсутствии префикса типа для переменной / массива, считать это “универсальным” префиксом и записывать / читать значение “как есть”.
Сейчас, если в переменной $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.
Появились ещё маленькие вопросы, которые можно отнести к разряду сохранений совместимости.
ARRTYPE сейчас возвращает пустую строку, если переменная/ячейка массива не существует. Что она будет возвращать в случае универсального префикса? Если универсальный префикс — это отсутствие префикса, проверка существования переменной через arrtype отпадает. Так что я пока этот момент в вики не трогаю, но заметку себе сделал.
MAX, MIN, и по-моему ещё пара функций, требуют указания префикса, емнип, для поиска среди определённого типа значений. Если для ARRPOS тип можно определить по типу значения, которое ищем, то для MAX это уже не выйдет. Для сохранения совместимости придётся считать, что MAX('A') это всегда поиск среди числовых значений, даже если автор поленился прописать префикс.
ARRTYPE совсем не поменяется - сейчас возвращается текущий фактический тип данных в ячейке массива. Если там не будет значения, то она также вернет пустую строку.
Про другие функции - нужно внимательно посмотреть на текущее поведение. Возможно, поведение min/max для массивов можно поменять - они редко используются и можно просто обновить игры.
ARRPOS используются часто, поэтому либо искать среди всех значений для пустого префикса (что может сломать часть игр), либо действительно менять поведение в зависимости от типа аргумента (что противоречит текущему подходу в языке), либо искать среди чисел при отсутствии префикса типа. Обработка только числовых значений при отсутствии префикса мне не нравится, конечно - это не согласуется с идеей универсального префикса.
Если какая-то функция совсем плохо вяжется с новой концепцией, проще, наверное, добавить новую функцию, а старую оставить, но депрекейтнуть. Например, наряду с arrpos ввести arrindex, которая будет совместима с универсальным префиксом.
Не хочется плодить плюс-минус одинаковые функции, к тому же депрекейтнуть - это значит поддерживать на неопределенный срок. Совсем в крайнем случае это можно сделать, конечно.