PDA

Просмотр полной версии : [Info] switch-выражения (?)



Daniel_Cortez
03.01.2020, 21:32
Внимание: В данной статье сравниваются с Pawn и критикуются некоторые черты языков Java и C#.
Если вы очень любите C# или Java - возможно, вы захотите закрыть эту страницу. Я вас предупредил.

Всем привет.

Ещё летом прошлого года я копался в моде NGRP (https://github.com/NextGenerationGamingLLC/SA-MP-Development), проверяя правильность работы warning 210 (https://pro-pawn.ru/showthread.php?16839-warning-210-%28-%29) и ещё пары других экспериментальных фич для компилятора, и случайно наткнулся на следующий код:

// Файл: acceptcancel.pwn
// Примечание: это код из работы механика. В зависимости от уровня своих умений (скилла)
// механик может заправить машину только на определённое количество литров.
new Float: fueltogive;
switch(PlayerInfo[playerid][pMechSkill])
{
case 0 .. 49: fueltogive = 2.0;
case 50 .. 99: fueltogive = 4.0;
case 100 .. 199: fueltogive = 6.0;
case 200 .. 399: fueltogive = 8.0;
default: fueltogive = 10.0;
}

Выглядит немного раздуто, не так ли? Во всех ветках case происходит присвоение значения к одной и той же переменной, отличается только само присваеваемое значение.
При взгляде на такой код люди, знакомые с Java и C#, наверняка вспомнят недавнее новшество этих языков - switch-выражения.
switch-выражения в Java (https://habr.com/ru/post/443464/)
switch-выражения в C# (https://docs.microsoft.com/ru-ru/dotnet/csharp/whats-new/csharp-8#switch-expressions)

Если вкратце, то эта фича позволяет использовать оператор switch внутри выражений, тем самым упрощая повседневную жизнь для программистов.
А что, если в Pawn попробовать осуществить что-то подобное?


Подход в стиле Java


С Java-подобными switch-выражениями приведённый выше пример можно было бы переписать примерно так:

new Float: fueltogive = switch(PlayerInfo[playerid][pMechSkill])
{
case 0 .. 49 -> 2.0;
case 50 .. 99 -> 4.0;
case 100 .. 199 -> 6.0;
case 200 .. 399 -> 8.0;
default -> 10.0;
};

Сразу бросаются в глаза следующие изъяны:
Фигурные скобки внутри выражения выглядят очень неестественно, т.к. обычно в Pawn их нельзя использовать внутри выражений.
В Pawn нет оператора "->". Если принять вариант с Java-подобным синтаксисом, придётся добавлять этот оператор как новый. Стоит ли оно того? Едва ли.
Синтаксис всё ещё выглядит раздутым из-за ключевых слов "case" и "default". Если у вас есть switch-выражение с небольшим количеством кейсов, у вас не получится так просто уместить его в одну-две строки.

Вывод: не годится. Мало того, что такой синтаксис не удобен, так он ещё и совершенно не похож на Pawn.



Подход в стиле C#


В C# поступили немного грамотнее, догадавшись, что ключевые слова "case" и "default" могут мешаться при записи switch-выражений в одну строку, и поэтому заменили "default" на более лаконичный вариант "_", а от "case" и вовсе отказались.

new Float: fueltogive = PlayerInfo[playerid][pMechSkill] switch
{
0 .. 49 => 2.0,
50 .. 99 => 4.0,
100 .. 199 => 6.0,
200 .. 399 => 8.0,
_ => 10.0
};

В Pawn оператор "_" уже можно использовать в вызовах функций, когда нужно пропустить какой-то аргумент (пример: "gettime(_, minutes, seconds)"), т.е. этот оператор уже означает "по умолчанию", благодаря чему использование "_" вместо "default" в switch-выражениях выглядит интуитивно понятным.
Кроме того, с таким лаконичным синтаксисом уже есть смысл не расписывать каждый кейс на отдельной строке, и можно попробовать записать выражение покомпактнее:

new Float: fueltogive = PlayerInfo[playerid][pMechSkill] switch {
0..49 => 2.0, 50..99 => 4.0, 100..199 => 6.0, 200..399 => 8.0, _ => 10.0
};

или даже так:

new Float: fueltogive = PlayerInfo[playerid][pMechSkill] switch
{ 0..49 => 2.0, 50..99 => 4.0, 100..199 => 6.0, 200..399 => 8.0, _ => 10.0 };

Но и этот стиль не лишён своих недостатков:
Фигурные скобки никуда не делись, и внутри выражения они всё так же не к месту.
Опять же, новый оператор "=>" вместо "->" из Java. Меняем шило на мыло.

Вывод: есть небольшой прогресс в сравнении с Java-подобным вариантом, но для Pawn такой стиль всё ещё плохо подходит. Можно сделать и лучше.



Собственный вариант?


Не всё, что хорошо в языке A, укладывается в языке B, и те два стиля switch-выражений, рассмотренные выше - прекрасное тому доказательство.
Но каким же тогда может быть если и не идеальный для большинства пользователей, то хотя бы приемлемый для реалий Pawn синтаксис?
Отличия от обычного ветвления switch допустимы, но в меру.
Pawn - это, прежде всего, язык для новичков, поэтому не стоит излишне усложнять его синтаксис; switch-выражения должны всё ещё выглядеть и восприниматься как Pawn, а не как какой-то новый язык.
Использование стрелок из Java и C# ("->" и "=>") вместо ":" мало того, что не укладывается в этот принцип, так ещё и не даёт абсолютно никакой выгоды.
Фигурные скобки тоже можно заменить на круглые, в Pawn внутри выражений они будут выглядеть куда естественнее.
Лаконичность.
В этом плане можно перенять идею из C# с отказом от "case" и заменой "default" на "_".
Да, это будет заметным отличием от обычного switch, но и что с того? В Pawn же нельзя использовать if внутри выражений - вместо этого есть тернарные выражения. Примерно то же самое и здесь, ничего особенного.


Моё мнение может отличаться от мнения большинства (а может и нет), но удобным и в то же время естественным в Pawn будет выглядеть такой синтаксис:

new result = switch (x;
1..10: expr1;
11..30: expr2;
31..60: expr3;
_: expr4;
);

В компактной записи:

new result = switch(x; 1..10: expr1; 11..30: expr2; 31..60: expr3; _: expr4);

Синтаксис отчасти позаимствован из цикла for, где тоже 3 выражения взяты в круглые скобки и отделяются друг от друга точкой с запятой. Иными словами, помимо скобок и отказа от case и default в синтаксисе ничего не изменено.

Если же взять более ранний пример с работой механика, то его можно было бы записать примерно так:

new Float: fueltogive = switch (PlayerInfo[playerid][pMechSkill];
0 .. 49: 2.0;
50 .. 99: 4.0;
100 .. 199: 6.0;
200 .. 399: 8.0;
_: 10.0;
);

или так:

new Float:fueltogive = switch (PlayerInfo[playerid][pMechSkill];
0..49: 2.0; 50..99: 4.0; 100..199: 6.0; 200..399: 8.0; _: 10.0);




От слов к делу


Как вы уже могли догадаться, все эти рассуждения приведены здесь не просто так. Я работал над switch-выражениями в Pawn примерно ещё с октября, и сейчас, в честь нового года, думаю, не будет зазорным позволить попробовать эту фичу в действии тем немногим, кто читает эту тему.

Тестовый билд: https://www.dropbox.com/s/39s6h80ho89k9vk/pawncc-3.10.9-switch-expr.zip?dl=0

Имейте в виду: этот билд предоставляется чисто в ознакомительных целях и не предназначен для повседневного использования.
Ни в коем случае не пытайтесь всерьёз использовать switch-выражения в своём моде (и уж тем более в каком-нибудь публичном инклуде/моде/фильтрскрипте), т.к. эта фича ещё не включена в следующий релиз компилятора официально (и не факт, что вообще когда-нибудь туда попадёт).

Daniel_Cortez
03.01.2020, 21:34
Также очень хотелось бы знать мнение читателей по следующим вопросам:


Стоит ли вообще предлагать эту фичу к включению в компилятор?
Ок, на самом деле здесь я на 99% уверен, что люди, управляющие репозиторием компилятора на GitHub, будут против и аргументируют это тем, что такая фича излишне усложняет язык Pawn.
Но всё же было бы здорово узнать, насколько мнение сообщества отличается от мнения четырёх человек на GitHub.
Стоит ли того упрощение кода с помощью switch-выражений, или же цена (усложнение синтаксиса языка) слишком велика?


Возможно, я выбрал не самый удачный синтаксис?
Если так, то не стесняйтесь предложить свой вариант (при условии, что аргументируете свой выбор).

Pa4enka
03.01.2020, 23:08
Хм, а чем старый вариант не устраивает(имеется ввиду вариант с мода NGRP)? Генерируются лишние инструкции присвоения?

new Float: fueltogive = switch(PlayerInfo[playerid][pMechSkill]:
0 .. 49 = 2.0; 50 .. 99 = 4.0; 100 .. 199 = 6.0; 200 .. 399 = 8.0; _= 10.0
);
Таким образом, используя ":", интуитивно понятно, что мы отталкиваемся от такой-то переменной/массива и исходя от ее значения происходит присвоения какого-то числа. Кроме того, использования знака "=" для скриптера здесь наиболее логичней, нежели его отсутствия. Хотя все же такой синтаксис выглядит довольно смешно для павн, имхо. Но в целом, если от этой фичи будет какой-то профит, то привыкнуть будет не сложно к любому синтаксису.

vvw
04.01.2020, 14:48
Идея отличная. Еще можно if запихнуть, чтобы можно было нормально различать if-elseif-else вместо тернарного оператора. Мне этот синтаксис более приятен, чем старый.. вероятно это из-за разработке на расте. Можно не изменять синтаксис, а сделать отдельный оператор __switch или __match, который будет поддерживать свой собственный синтаксис.. Таким образом мы не нарушим стандарт, но и сделаем расширение в языке.

MassonNN
05.01.2020, 00:49
new result = switch (x): {0..5: = 3, 6..10: = 4, 11..20: = 5};
1). Аналогичен стандартному.
2). Похож в использовании на множественное присвоение:



X=
Y=
Z= 0;


3). Чисто для меня: лучше делать функционал использования не принципиально новым, и не делать из оператора функцию (как в последнем примере), а все таки оставлять его оператором.

Daniel_Cortez
05.01.2020, 21:12
Еще можно if запихнуть, чтобы можно было нормально различать if-elseif-else вместо тернарного оператора. Мне этот синтаксис более приятен, чем старый.. вероятно это из-за разработке на расте.
Вероятно.


Можно не изменять синтаксис, а сделать отдельный оператор __switch или __match, который будет поддерживать свой собственный синтаксис.. Таким образом мы не нарушим стандарт, но и сделаем расширение в языке.
Не понимаю о чём ты. Зачем здесь "__switch", "__match" или ещё какое-то новое ключевое слово, если switch-выражения никаким боком не нарушают совместимость с уже существующим кодом на Pawn?



Pa4enka, MassonNN, искренне не понимаю вашей логики. Ощущение, будто никто из вас не дочитал статью до конца, пропустив мимо ушей ключевой посыл:

Pawn - это, прежде всего, язык для новичков, поэтому не стоит излишне усложнять его синтаксис; switch-выражения должны всё ещё выглядеть и восприниматься как Pawn, а не как какой-то новый язык.
В своём варианте я отчасти взял за основу синтаксис цикла for, в котором тоже 3 выражения взяты в скобки и отделяются друг от друга с помощью точек с запятой (возможно, мне стоило сделать это пояснение сразу, я уже добавил его в статью). Иными словами, от стандартного синтаксиса отличий минимум.

А теперь...

new Float: fueltogive = switch(PlayerInfo[playerid][pMechSkill]:
0 .. 49 = 2.0; 50 .. 99 = 4.0; 100 .. 199 = 6.0; 200 .. 399 = 8.0; _= 10.0
);
Какое отношение такой синтаксис вообще имеет к Pawn?
В чём профит от придания знаку "=" нового значения (отделение значений кейса от выражения)? Чем вместо этого не устроил знак ":"? Или знак "=" призван показать присваивание? Так ведь знак присваивания уже есть перед словом switch.




new result = switch (x): {0..5: = 3, 6..10: = 4, 11..20: = 5};
См. выше. Не вижу смысла повторяться, тем более что всё это просто походит на попытку троллинга.

vvw
06.01.2020, 19:45
Не понимаю о чём ты. Зачем здесь "__switch", "__match" или ещё какое-то новое ключевое слово, если switch-выражения никаким боком не нарушают совместимость с уже существующим кодом на Pawn?

У думал, ты хочешь добавить доп функционал к оператору. Тогда извините.

Кстати, ты открыл issue с этой фичей? Может быть там лучше обсудить синтаксис оператора?

MassonNN
07.01.2020, 23:27
Это ты меня не понял. Как раз таки я не добавил никакого новшества в switch, я напротив за этим и написал, что твой вариант менее похож на оригинальный:


В развернутом виде это будет выглядеть так:

new result = switch (x) {
0..5: = 3;
6..10: = 4;
11..20: = 5;
}



По поводу знака равно. Здесь я уже сказал, что это тоже напоминает множественное присвоение, какое есть в pawn




Объясняю, почему мне не нравится твой вариант:


Моё мнение может отличаться от мнения большинства (а может и нет), но удобным и в то же время естественным в Pawn будет выглядеть такой синтаксис:
new result = switch (x;
1..10: expr1;
11..30: expr2;
31..60: expr3;
_: expr4;
);


Дело в том, что теперь switch выглядит как ФУНКЦИЯ, а не оператор. Не мне тебе объяснять в чем разница и почему логичнее было бы оставить синтаксис ОПЕРАТОРА)

vvw
08.01.2020, 22:33
Это ты меня не понял. Как раз таки я не добавил никакого новшества в switch, я напротив за этим и написал, что твой вариант менее похож на оригинальный:


В развернутом виде это будет выглядеть так:

new result = switch (x) {
0..5: = 3;
6..10: = 4;
11..20: = 5;
}



По поводу знака равно. Здесь я уже сказал, что это тоже напоминает множественное присвоение, какое есть в pawn




Объясняю, почему мне не нравится твой вариант:



Дело в том, что теперь switch выглядит как ФУНКЦИЯ, а не оператор. Не мне тебе объяснять в чем разница и почему логичнее было бы оставить синтаксис ОПЕРАТОРА)

Языки всегда улучшаются, взаимстуют синтаксис из других языков, поэтому это нормально, что оператор может выглядеть как функция (кстати, почему как функция?). Сейчас мода пошла на взаимстование синтаксиса из ФП.

MassonNN
09.01.2020, 00:11
Языки всегда улучшаются, взаимстуют синтаксис из других языков, поэтому это нормально, что оператор может выглядеть как функция (кстати, почему как функция?). Сейчас мода пошла на взаимстование синтаксиса из ФП.

Я может не так выразился. Я имею ввиду оператор это оператор:


if () {} else {}



for(new i; i < i+1; i++) {}

А функция это функция и думаю не нужно путать их синтаксис:


SendClientMessage(playerid, 0x00110011FF, !"Cho");


Просто для новичков думаю это усложнение и перемешка только усложнит понимание ЯП Pawn (которое создавалось как простое)

Daniel_Cortez
09.01.2020, 02:35
Это ты меня не понял. Как раз таки я не добавил никакого новшества в switch, я напротив за этим и написал, что твой вариант менее похож на оригинальный:


В развернутом виде это будет выглядеть так:

new result = switch (x) {
0..5: = 3;
6..10: = 4;
11..20: = 5;
}



По поводу знака равно. Здесь я уже сказал, что это тоже напоминает множественное присвоение, какое есть в pawn

Я тебя прекрасно понял, ибо примерно так же Pa4enka объяснил свой вариант.
Ок, тогда переформулирую вопрос: в чём необходимость использования знака "="? Нет, не цель (отделить кейс от выражения и показать присваивание), а именно необходимость? Почему это так важно в твоём варианте? Сильно сомневаюсь, что кто-то станет путаться без этого знака.



Дело в том, что теперь switch выглядит как ФУНКЦИЯ, а не оператор.
И что с того? sizeof, defined, tagof и __emit тоже "похожи на функции", потому что могут использоваться с круглыми скбоками (настолько похожи, что некоторые уникумы на одном печально известном Pawn-форуме могут полдня с пеной у рта доказывать тебе, что sizeof занимает время на выполнение и его следует избегать, особенно внутри format()). Ничего нового в этом нет.

Кстати говоря про __emit, с этим оператором как раз и было лишний раз подтверждено правило, что в выражениях должны использоваться круглые скобки

new x = __emit(lctrl 6, add.c 20);

а вне выражений - фигурные

new x;
__emit
{
lctrl 6
stor.s.pri x
}

Это уже закрепившийся синтаксис, поэтому рассматривать возможность фигурных скобок в switch-выражениях считаю заведомо бессмысленным.

Pa4enka
09.01.2020, 03:49
Сильно сомневаюсь, что кто-то станет путаться без этого знака.
Станут. По крайней мере на начальном этапе. Как и ранее говорилось, отсутствия этого знака ставит обычного скриптера в ступор. Мне например понадобилось пересмотреть твой код 3 раза, дабы понять о чем идет речь(и это с учетом твоих объяснений). При этом важно понимать в каком обществе мы находимся. Большинство скриптеров совершенно не понимают другие языки программирования, а некоторые вообще не пытаются вникать. Остальная часть, к который ты и относишься, пишет на многих языках и тем самым какие-то стилистические аспекты ты видишь с другого угла. Поэтому и необходимость для нас разная.

Я в некотором роде согласен с vvw, что возможно бы стояло добавить новую функцию, а не расширять возможности оператора switch, ибо тогда теряется весь смысл switch. Но с другой стороны, зачем добавлять новую функцию ради одного лишь нового синтаксиса? Тоже спорный вопрос.


Какое отношение такой синтаксис вообще имеет к Pawn?
В чём профит от придания знаку "=" нового значения (отделение значений кейса от выражения)? Чем вместо этого не устроил знак ":"? Или знак "=" призван показать присваивание? Так ведь знак присваивания уже есть перед словом switch.
Первый знак присвоения стоит для того, чтобы показать, что функция switch возвратит определенное значения. В нашем случае, результат выражения switch. Разве нет?)


всё это просто походит на попытку троллинга.
Нет, ты указал, что хочешь получить варианты синтаксиса от юзеров. Вот мы и пытаемся вставить свои "пять копеек" :)

MassonNN
09.01.2020, 11:22
Я тебя прекрасно понял, ибо примерно так же Pa4enka объяснил свой вариант.
Ок, тогда переформулирую вопрос: в чём необходимость использования знака "="? Нет, не цель (отделить кейс от выражения и показать присваивание), а именно необходимость? Почему это так важно в твоём варианте? Сильно сомневаюсь, что кто-то станет путаться без этого знака.



И что с того? sizeof, defined, tagof и __emit тоже "похожи на функции", потому что могут использоваться с круглыми скбоками (настолько похожи, что некоторые уникумы на одном печально известном Pawn-форуме могут полдня с пеной у рта доказывать тебе, что sizeof занимает время на выполнение и его следует избегать, особенно внутри format()). Ничего нового в этом нет.

Кстати говоря про __emit, с этим оператором как раз и было лишний раз подтверждено правило, что в выражениях должны использоваться круглые скобки

new x = __emit(lctrl 6, add.c 20);

а вне выражений - фигурные

new x;
__emit
{
lctrl 6
stor.s.pri x
}

Это уже закрепившийся синтаксис, поэтому рассматривать возможность фигурных скобок в switch-выражениях считаю заведомо бессмысленным.

И все таки я выступаю за сохранения синтаксиса оператора. Ну просто по такой логике sizeof, tagof и так далее, они уже изначально объявлены так, функционально. А оператор switch объявлен оператором по синтаксису. Я просто хочу сохранить логику синтаксиса и не вносить в нее изменений (либо делать это очень незначительно и интуитивно). По поводу знака равно: здесь я тоже сомневаюсь, и на самом деле я редактировал свое первое сообщение несколько раз, думая как будет лучше. Вообще без знака равно и со знаком для меня выглядит нормально, но с другой стороны в pawn, наверное, этот знак был ни к чему, поэтому я не говорю про необходимость этого знака

P.s. к слову добавление такого расширения было бы очень кстати :)