Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Правила языка: строковые литералы

Строковые литералы — это одна или несколько последовательностей символов, заключённых в двойные кавычки. Эти символы могут иметь как обычное текстовое представление, так и двоичное, десятичное или шестнадцатиричное представление.

Символы в двоичном представлении внутри строковых литералов

Последовательность символов в двоичном представлении 

    =  2   \  { Двоичная цифра }1

Примеры:

  2\00001001			// символ табуляции
  2\01111110			// символ ~
  2\00100001			// символ !
  2\00111111			// символ ?

Если число двоичных цифр меньше 8, то левые разряды считаются равными 0:

  2\1001			// символ табуляции
  2\1111110			// символ ~
  2\100001			// символ !
  2\111111			// символ ?

Если число двоичных цифр больше 8, то девятая и далее цифры не считаются двоичными цифрами, из которых складывается код символа, а воспринимаются, как обычный текст:

  2\011111101010		// символы ~1010
  2\0010000101			// символы !01
  2\001111111			// символы ?1

Символы в двоичном предсталении могут идти подряд:

  2\111111012\1000012\111111	// символы ~!?

Когда они идут подряд, то «2» не только можно, но и желательно опускать. Тогда «\» выступает в роли разделителя:

  2\11111101\100001\111111	// символы ~!?

Символы в десятичном представлении внутри строковых литералов

Последовательность символов в десятичном представлении 

    =  1   0   \  { Десятичная цифра }1

Примеры:

  10\009			// символ табуляции
  10\126			// символ ~
  10\033			// символ !
  10\063			// символ ?

Если число двоичных цифр меньше 3, то левые разряды считаются равными 0:

  10\9				// символ табуляции
  10\33				// символ !
  10\63				// символ ?

Числовое значение десятичного представления символа должно быть в диапазоне от 0 до 255. Если оно превышает значение 255, то третья цифра после символа «\» трактуется, как обычный текст:

  10\777			// символы M7 (т.е. 10\77 и цифра «7»)

Если число десятичных цифр больше 3, то четвёртая и далее цифры не считаются цифрами, из которых складывается код символа, а воспринимаются, как обычный текст:

  10\126127			// символы ~127
  10\123456			// символы {456
  10\11111			// символы o11

Символы в десятичном предсталении могут идти подряд:

  10\12610\12310\111		// символы ~{o

Как и в случае с двоичным предсталением, для второго и далее символа обозначение «10» удобнее пропускать. Тогда «\» выступает в роли разделителя:

  10\75\38\82			// символы K&R

Символы в шестнадцатиричном представлении внутри строковых литералов

Последовательность символов в шестнадцатиричном представлении 

    =  1   6   \  { Шестнадцатиричная цифра }1

Примеры:

  16\09				// символ табуляции
  16\7e				// символ ~
  16\21				// символ !
  16\3f				// символ ?

Если число шестнадцатиричных цифр не две, а одна, то недостающая левая цифра считаются равной 0:

  16\9				// символ табуляции
  16\d				// «перевод строки»
  16\a				// «возврат каретки»

Если число шестнадцатиричных цифр больше 2, то третья и далее цифры не считаются шестнадцатиричными цифрами, из которых складывается код символа, а воспринимаются, как обычный текст:

  16\7ef			// символы ~f
  16\2233			// символы "33
  16\2FACE			// символы /ACE

Символы в шестнадцатиричном предсталении могут идти подряд:

  16\2716\2A16\27		// символы '*'

Шестнадцатиричное предсталение, как и ранее описанные, может иметь сокращённую форму, когда обозначение «16» опущено, «\» играет роль разделителя:

  16\22\7B\7D\22			// символы "{}"
  16\59\4F\50\52\53\54			// символы YOPRST

Обычный текст внутри строковых литералов

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

  Обычный текст с ""			// равносильно «Обычный текст с "»
  Сказано: ""Иди!""			// равносильно «Сказано: "Иди!"»

Строковый литерал

Это правило подводит итог описанному выше:

Последовательность символов 

  = Последовательность символов в двоичном представлении

  | Последовательность символов в десятичном представлении

  | Последовательность символов в шестнадцатиричном представлении

  |  "   " 

  |  любой символ 

Литерал — это одна или несколько последовательностей символов. Эти последовательности заключены в двойные кавычки и разделены между собой пустым оператором. Если не разделены пустым оператором, то тогда, как описано выше, две идущие подряд кавычки трактуются как одна.

Строковый литерал 

    =  "  Последовательность символов  " 

    { Пустой оператор  "  Последовательность символов  "  }

В примерах ниже все строковые литерали имеют одно и то же значение:

  "compiler.su"

  "2\1100011\1101111\1101101\1110000\1101001\1101100\" ...
    (*продолжение*)  "2\1100101\1110010\101110\1110011\1110101"

  "10\99\111\109\112\105" (*разрыв*) "10\108\101\114\46\115\117"

  "16\63\6f\6d\70\69\6c\65\72\2e\73\75"

  "2\1100011\1101111\1101101p10\105\108\101\r" "16\2e\73\75"

Символьный литерал

Символьный литерал — это одиночный символ. Он заключен в одинарные кавычки, либо представлен в числовой форме. Второй вариант связан с тем, что он короче. Например, 16\30 без апострофов выглядит короче, чем '16\30' с апострофами.

Символьный литерал

    =  '   Любой символ   ' 

    | Константа в двоичном виде

    | Константа в десятичном виде

    | Константа в шестнадцатиричном виде

Примеры:

  a = 'A'

  звёздочка = '*'

  двойной апостроф = '"'

  пробел = 32;

  цифра ноль = 16\30

  вопросительный знак = 2\111111

Опубликовано: 2023.05.02, последняя правка: 2023.09.22    00:18

ОценитеОценки посетителей
   ████████████████ 3 (37.5%)
   ██████ 1 (12.5%)
   ███████████ 2 (25%)
   ███████████ 2 (25%)

Отзывы

✅  2023/05/22 10:25, kt          #0 

Я бы ещё добавил коэффициент повторения для текстовых констант, аналогично PL/1, например:
dcl x bit(64) static init('1'(64)b); // все единицы 
put skip list('='(50),'Начало','='(50); // заголовок из равенств 

✅  2023/05/23 00:51, Автор сайта          #1 

Думал над этой фичей, но простых вариантов в голову не пришло. Решил, что лучше обойтись (Си обходится же), сделать язык сложным успеется всегда. Впрочем, можно использовать строковую функцию с условным названием «размножить», которая с константными аргументами отработает во время компиляции, результат будет таким же.

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

✅  2023/05/29 00:00, alextretyak          #2 

А чем не устраивает решение как в Python, а именно оператор умножения (*)?
s = 5 * '-' # равнозначно s = '-----'

✅  2023/05/29 22:08, Автор сайта          #3 

Философия Питона предполагает принцип наименьшего удивления. Однако ожидания прикладных программистов на Питоне не совпадает с ожиданиями системных программистов на Си или Rust. Системные программисты помнят, что литера '-' соответствует числу 45. Поэтому 5 * '-' эквивалентно 5 * 45 и даёт 225. Если Вы попробуете, то убедитесь в этом.

Допустим, мы поменяем '-' на "-", чтобы иметь дело не с отдельной литерой, а со строкой, длина которой 1. Но что такое 5 умножить на число n? Это сложить 5 с самой собой n раз. Но как сложить 5 с самой собой "-" раз? Ведь "-" не является числом.

Было бы логичнее записать "-" * 5, тогда бы это значило "-" + "-" + "-" + "-" + "-". Это уже ближе к истине, если рассматривать операцию «+» как склейку строк. Но в любом случае надо 7 раз отмерить. Пока что острой необходимости в этом в первой версии нет, торопиться не будем.

Посмотрел раздел «Строковые литералы» на Вашем сайте, там эта тема не затронута :(

✅  2023/05/30 06:06, MihalNik          #4 

Однако ожидания прикладных программистов на Питоне не совпадает с ожиданиями системных программистов на Си или Rust. Системные программисты помнят, что литера '-' соответствует числу 45. Поэтому 5 * '-' эквивалентно 5 * 45 и даёт 225.

А что, Rust слабо типизирован?

✅  2023/05/30 23:27, Автор сайта          #5 

Нет, он не слабо типизирован. Но в отличие от Си, у него нет автоматического преобразования целых типов меньшей разрядности к целым типам большей разрядности. Например, в Си тип char (к которому принадлежит символьный литерал '-') на лету становится типом int, поэтому сложив символы ' ' и '!', автоматом получаем 65 (32 + 33).

В Rust нет такого автоматического преобразования. Приведение типов надо делать явно. Поэтому, чтобы получить 225 в результате умножения 5 и дефиса, нужно написать чуть больше:
fn main() {
println!("5 * '-' = {}", 5 * '-' as i32);
}
Выводит:
5 * '-' = 225
Проверить это можно в online-компиляторе: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021

Символьный литерал '-' как в Си, так и в Rust имеет тип char (не путать со строкой "-" с длиной 1).

✅  2023/05/31 00:00, alextretyak          #6 

литера '-' соответствует числу 45. Поэтому 5 * '-' эквивалентно 5 * 45 и даёт 225.

Да, верно. А я уже и забыл про такую особенность языка Си. Но дело в том, что на практике такая "особенность" никогда не требуется, и связана скорее всего с тем, что в Си было невозможно ввести [отдельный/]полноценный тип для символа (который было бы запрещено умножать на число), поэтому тип char по факту совпадает с типом int8_t. Такую "фичу" я бы смело добавил в «Признаки устаревшего языка».

В Rust нет такого автоматического преобразования. Приведение типов надо делать явно.

Да, а вот в Java и C#, на удивление, оставлено поведение Си (могли бы хоть warning/предупреждение выдавать в таких случаях).

Посмотрел раздел «Строковые литералы» на Вашем сайте, там эта тема не затронута :(

Не затронута, т.к. конструкции вида 5 * "-" не являются строковыми литералами. Также как 5 * 45 не является целочисленным литералом, хотя и является целочисленным константным выражением (integral constant expression) и может использоваться везде, где могут использоваться только целочисленные константы/литералы. В 11l, также как в Python, можно умножать число на строку (в т.ч. на строковый литерал).

И хочу добавить, что разделение символьных и строковых литералов мне категорически не нравится. Например тем, что можно написать s.find('\n'), а можно s.find("\n"), что даёт одинаковый результат, но при этом первая запись будет эффективнее. Поэтому в 11l такого разделения нет, а есть "сырые" (например ‘\’) и "не сырые", например "\\", что равнозначно ‘\’) строковые литералы, фактический тип которых (Строка либо Символ) определяется при компиляции в зависимости от контекста использования этого литерала:
пер м1 = [‘а’, ‘б’, ‘в’] // это массив символов
пер м2 = [‘а’, ‘бб’, ‘в’] // а это массив строк

пер с = ‘-’ // это строка; если требуется символ, то следует писать пер с = Символ(‘-’)
вывод(‘-’.код) // выведет 45 (в таком контексте ‘-’ считается символом, а не строкой)

пер м3 = [Строка(‘а’), ‘б’, ‘в’] // это массив строк
Вообще, я подумываю над тем, чтобы строковые литералы, состоящие всего из одного символа (т.е. длина которых равна 1), всегда преобразовывались в тип Символ. В таком случае вместо пер с = ‘-’ в примере выше придётся писать пер с = Строка(‘-’) или Строка с = ‘-’. Но для принятия однозначного и окончательного решения мне пока ещё не хватает данных.

Пока что острой необходимости в этом в первой версии нет

Да, умножение числа на строку требуется достаточно редко, но вот аналогичное умножение булевой переменной/выражения на строку нужно намного чаще (например, в коде https://sourceforge.net/p/pqmarkup/code/ci/default/tree/pqmarkup.py моей реализации pqmarkup оно встречается 15 раз).

✅  2023/05/31 04:00, alextretyak          #7 

Но дело в том, что на практике такая "особенность" никогда не требуется

Что здесь я имею в виду? Речь только про умножение символьного литерала на число. Или умножение числа на символьный литерал, т.к. сложение символьного литерала с числом имеет практическое применение. Например: '0' + i для получения символа-цифры i (при этом i должно быть от 0 до 9) или 'a' + n для получения n-ной буквы латинского алфавита.

✅  2023/05/31 22:25, Автор сайта          #8 

Тип char по факту совпадает с типом int8_t. Такую "фичу" я бы смело добавил в «Признаки устаревшего языка».

Было бы интересно услышать от Вас аргументы в пользу этого. Если такие типы эквивалентны, то в чём становится хуже?

А вот в Java и C#, на удивление, оставлено поведение Си

За автоматическое приведение целых типов с меньшей разрядностью к целым типам с большей разрядностью, которые не искажает значения, есть серьёзный аргумент. Они делают код короче, освобождают от лишних несущественных деталей. Другое дело, если автоматические приведения искажают значения. Тогда да, с этим надо бороться. Если чётко провести границу между искажающими и неискажающими преобразованиями, то польза в виде краткости кода вполне очевидна. А какие аргументы против Вы видите?

Посмотрел раздел «Строковые литералы» на Вашем сайте, там эта тема не затронута :(

Не затронута, т.к. конструкции вида 5 * "-" не являются строковыми литералами.

Запутался в терминологии, имел в виду символьные литералы, которые искал в разделе «Строковые литералы». Выше на этой странице описаны оба вида литералов, того же ожидал от вашего сайта. Но не нашёл у Вас описания именно символьных литералов.

разделение символьных и строковых литералов мне категорически не нравится

Ну так объединяйте их. Есть же языки, где строка единичной длины эквивалентна строковому литералу. Да, небольшая потеря эффективности процессора, зато небольшое увеличение эффективности программиста. Множество языков придерживаются такой философии. Если Вы позиционируете свой язык в этой же нише, то почему бы и нет?
пер м1 = [‘а’, ‘б’, ‘в’] // это массив символов
. . .
пер с = ‘-’ // это строка; если требуется символ, то следует писать пер с = Символ(‘-’)
По какой причине в первой строке делается вывод, что это массив символов? Там же не написано «Символ(‘<литера>’)».

чтобы строковые литералы, состоящие всего из одного символа (т.е. длина которых равна 1), всегда преобразовывались в тип Символ.

Тут ещё надо определиться: эта длина, равная 1, должна быть известна во время компиляции, чтобы принимать решение именно в это время? Или же во время исполнения тоже.

умножение числа на строку требуется достаточно редко

Ещё раз хочу расставить точки над «ё». Если 5 умножаем на строку, то множимое тут — 5, а строка — множитель. Умножается (то есть размножается!) пятёрка, а не строка. Логичнее, если строка бывает множимым, то есть размножаемым. Если умножаем множимое на множитель, то размножается первое, а не второе. Тот случай, когда перемена мест имеет значение.

умножение булевой переменной/выражения на строку нужно намного чаще

Мне кажется, что оператор Элвиса вполне подходящ в таких случаях. Кстати, как Вы относитесь к автоматическим преобразованиям значений в булев тип и обратно? Типа 0 — «ложь», всё остальное — «истина». И наоборот, «ложь» автоматически превращается в 0, вот только во что превращается «истина» и почему? И следует ли считать такие автоматические преобразованием свидетельством устаревания языка?

сложение символьного литерала с числом имеет практическое применение. Например: '0' + i для получения символа-цифры i (при этом i должно быть от 0 до 9) или 'a' + n для получения n-ной буквы латинского алфавита.

Вам теперь будет над чем подумать, как совместить и то, и это: отсутствие символьных литералов с операциями над ними :)

✅  2023/06/01 09:33, MihalNik          #9 

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

Логичнее, когда размножением строк получаются новые строки не зависимо от порядка следования.

умножение числа на строку требуется достаточно редко

Отсутствие удобного размножения строк на уровне языка в консольных приложениях выливается в полотна литералов псевдографики и отступов.

Типа 0 — «ложь», всё остальное — «истина». И наоборот, «ложь» автоматически превращается в 0, вот только во что превращается «истина» и почему? И следует ли считать такие автоматические преобразованием свидетельством устаревания языка?

Их следует считать слабой типизацией.

✅  2023/06/01 23:18, Автор сайта          #10 

Логичнее, когда размножением строк получаются новые строки не зависимо от порядка следования.

В том то и дело, что с точки зрения русского языка множится множимое. И занимается этим множитель. Но больше спорить не буду, у Вас есть не только точка зрения, но и право на неё.

Отсутствие удобного размножения строк на уровне языка в консольных приложениях выливается в полотна литералов псевдографики и отступов.

Но ситуация не столь катастрофична. В языке могут отсутствовать строковые операции времени компиляции, но скорее всего есть строковые функции, которые при константных аргументах вычисляемы при компиляции. Например, функции str_repeat (PHP) и replicate (Clipper) тоже размножают строки.

Кстати, в PHP 5 * '-', '-' * 5, 5 * "-" и "-" * 5 выдают одинаковый результат: 0. Везде разброд и шатания. Се ля ви.

✅  2023/06/02 00:00, alextretyak          #11 

Было бы интересно услышать от Вас аргументы в пользу этого. Если такие типы эквивалентны, то в чём становится хуже?

Во-первых, в том, что компилятор позволяет писать всякие глупости вроде сложения, умножения или даже деления символьных литералов или символьных переменных (аналогично, ни один язык программирования не позволяет производить такие операции над указателями, ведь они также не имеют смысла).

Во-вторых, специальный тип для символа позволяет снабдить его специфическими методами, такими как is_digit() или lowercase(), или даже операторами (например, операция вычитания для символов вполне допустима: если символ c является строчной латинской буквой, то c − 'a' даст номер этой буквы в алфавите. Кстати, для указателей операция вычитания также допустима и применима. Или операция сложения — только не двух символов, а сложение символа и числа. При этом тип результата разумно сделать символом, а не числом, как собственно уже и сделано в Kotlin.

И в-третьих, иногда требуется массив 8-разрядных целых чисел, и хочется, чтобы это было отражено средствами языка явно. Для чего? Ну, например, чтобы имея массив 8-разрядных целых при выводе его в консоль (либо при просмотре его в отладчике) он выводился/отображался именно как массив чисел, а не массив символов с непонятно какими кодами. Вот тут (https://cboard.cprogramming.com/cplusplus-programming/128808-1-byte-int-not-using-char.html) человек жалуется, что ему нужно однобайтовое целое число, но проблема в том, что std::cout выводит его как символ, и приходится кастить 8-разрядное число к short или int при его выводе.

За автоматическое приведение целых типов с меньшей разрядностью к целым типам с большей разрядностью, которые не искажает значения, есть серьёзный аргумент. ... А какие аргументы против Вы видите?

Нет, против такого автоматического приведения я ничего не имею. Я против трактовки символов/‘символьных литералов’ как целых чисел малой разрядности. И если даже ради совместимости с Си в Java и C# оставили такой архаизм, то могли бы отдельно проверить/обработать случай умножения символьного литерала на число.

А в Rust, между прочим, char является отдельным типом, причём 4-х байтовым, который автоматически не приводится ни к какому целочисленному типу. В этом отношении, я согласен с разработчиками Rust, а вот с тем, что в Rust отсутствует автоматическое приведение целых типов с меньшей разрядностью к целым типам с большей разрядностью, я не согласен. Также как и Вы, насколько я понимаю.

Но не нашёл у Вас описания именно символьных литералов.

Таких литералов в 11l нет, и описания, соответственно, тоже. :)(: Но описание правил автоматического преобразования строковых литералов к типу Символ, конечно, стоит добавить в документацию языка.

имел в виду символьные литералы, которые искал в разделе «Строковые литералы». Выше на этой странице описаны оба вида литералов, того же ожидал от вашего сайта.

Да, похоже, такое описание разумно добавить именно на эту страницу. Но пока я ещё окончательно не определился с тем, каковы должны быть правила такого автоматического преобразования, а потому и не спешу добавлять это в документацию.

Ну так объединяйте их. Есть же языки, где строка единичной длины эквивалентна строковому литералу.

Не совсем понятно, что вы имели в виду? Может «строка единичной длины эквивалентна символьному литералу»?

И о каких языках речь? Я что-то навскидку не могу припомнить такого поведения, хотя языков повидал немало.

Да, небольшая потеря эффективности процессора, зато небольшое увеличение эффективности программиста.

Я считаю, что в данном случае можно добиться увеличения эффективности программиста без потери производительности. В наиболее общем виде можно сформулировать такое правило: если строковый литерал единичной длины можно рассматривать как символьный литерал (ну т.е. если в этом случае не будет ошибок компиляции), тогда его нужно рассматривать именно как символьный литерал (и при необходимости приводить к типу Символ), иначе он приводится к типу Строка. Но опять же при необходимости: например при передаче строкового литерала в качестве аргумента функции совсем не обязательно создавать при вызове этой функции (в run-time) объект типа Строка, можно создать некий аналог объекта std::string_view, который состоит лишь из указателя и длины строки, и передавать в функцию именно его.

По какой причине в первой строке делается вывод, что это массив символов? Там же не написано «Символ(‘<литера>’)».

По причине того, что так захардкодено в компиляторе. :)(: Ну т.е. буквально так и делается: при компиляции проверяются все элементы массива, и если все они являются строковыми литералами единичной длины, значит это массив символов.

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

Тут ещё надо определиться: эта длина, равная 1, должна быть известна во время компиляции, чтобы принимать решение именно в это время? Или же во время исполнения тоже.

Я говорю в основном про этап компиляции, т.к. основная цель — обойтись без разделения на строковые и символьные литералы. А проверку на единичность длины строк во время исполнения лучше вынести в реализацию конкретных функций, где такая оптимизация имеет смысл (к примеру, та же s.find(some_string) в случае когда длина some_string равна 1, получит некоторое повышение производительности за счёт избавления от вложенного цикла по длине строки some_string — в этом случае вызов s.find(some_string) будет равнозначен s.find(some_string[0])).

Ещё раз хочу расставить точки над «ё». Если 5 умножаем на строку, то множимое тут — 5, а строка — множитель. Умножается (то есть размножается!) пятёрка, а не строка. ... Если умножаем множимое на множитель, то размножается первое, а не второе. Тот случай, когда перемена мест имеет значение.

Юрий, что вы мутите воду, ну не имеет перемена мест тут значения. :)(: А то иначе, следуя такой логике, получится, что 5x всегда означает сложить 5 с самой собой x раз. Но если x = 1.5 [или вообще, если x — это комплексное число!], то как сложить 5 с самой собой 1.5 раз? А вот сложить 1.5 с самой собой 5 раз прекрасно получается.

Мне кажется, что оператор Элвиса вполне подходящ в таких случаях.

Нет, Элвис же совсем про другое. Ведь Элвис — это урезанный тернарный оператор ?:, а здесь нужен полноценный. Т.е. речь о таком коде:
outfile.write("<table" + ' style="display: inline"'*is_inline + ">\n")
Без оператора умножения его пришлось бы писать на Python так:
outfile.write("<table" + (' style="display: inline"' if is_inline else '') + ">\n")
Или так на C++:
outfile.write("<table"s + (is_inline ? " style=\"display: inline\"" : "") + ">\n")
Вроде разница и небольшая, но читаемость кода с оператором умножения всё-таки немного лучше.

Кстати, как Вы относитесь к автоматическим преобразованиям значений в булев тип и обратно?

Отрицательно. За редкими исключениями (например, тип re:Match в 11l имеет такое автоматическое преобразование, впрочем, не исключено, что в будущих версиях языка я от него откажусь: функция RegEx.match() может возвращать не re:Match, а re:Match?, и тогда вместо if ...match(…) {...} нужно будет писать if ...match(…) != null {...}).

Типа 0 — «ложь», всё остальное — «истина».

Вот это вот «всё остальное» — это что? Это то, что не равно 0. Вот так, я считаю, и следует всегда писать: if int_var != 0 {...}, а просто if int_var {...} должно считаться ошибкой.

И наоборот, «ложь» автоматически превращается в 0, вот только во что превращается «истина» и почему?

Истину логично превращать в 1, т.к. именно такое значение на аппаратном уровне имеет истина\true в булевом типе {при записях вида bool b = true} во всех реализациях всех языков программирования, насколько мне известно. Что-то слышал про все единицы (речь про двоичное представление, разумеется), но в каком конкретно языке или в какой реализации такое, сказать не могу.

И следует ли считать такие автоматические преобразованием свидетельством устаревания языка?

По моему мнению, да. Даже в древнем Java, несмотря на его совместимость с Си во многих моментах (включая целочисленный тип char), числа не преобразуются автоматически в булев тип и обратно (и написать if (0) ... или if (1) ... в Java нельзя).

Вам теперь будет над чем подумать, как совместить и то, и это: отсутствие символьных литералов с операциями над ними

Ну такое отсутствие весьма условно. Я бы назвал это автоматическое определение вида литерала при компиляции, или строкосимвольные литералы.

Но даже при отсутствии символьных литералов ничто не помешает писать "a"[0]. А насчёт операций над символьными литералами в 11l уже есть всё что нужно. Вот слегка модифицированная строчка из кода реализации шифра Цезаря (приводится в примерах на главной странице 11l-lang.org/ru):
r[L.index] = Char(code' (c.code - ‘a’.code + key) % 26 + ‘a’.code)
Хотя, признаю, что на Си это записывается короче:
r[L_index] = (c - 'a' + key) % 26 + 'a';

✅  2023/06/02 01:00, alextretyak          #12 

Кстати, в PHP 5 * '-', '-' * 5, 5 * "-" и "-" * 5 выдают одинаковый результат: 0.

Это смотря в какой версии PHP. В PHP 8 такое умножение является ошибкой. А в версии 7.1.0 и выше (до 8.0) выдаётся предупреждение. Вот хороший сайт, благодаря которому я это выяснил: https://onlinephp.io (там можно раскрыть группу ‘PHP Versions’ и поставить галочки на множестве версий PHP, чтобы проверить поведение одного и того же кода на разных версиях)

Везде разброд и шатания.

Пока да. Но я вижу довольно устойчивую тенденцию движения/эволюции языков программирования в сторону некоторого консенсуса. Наиболее удачные решения из одних языков постепенно проникают в другие {например, starts_with в C++ 20 и std::format c синтаксисом строки форматирования в стиле Python, в Java 8 наконец появились лямбды, в Java 10 — ключевое слово var}. Ошибки проектирования постепенно устраняют в новых версиях (хорошо заметно на том же PHP). Правда, при этом, в новые версии языков добавляют много ненужного хлама... но это уже другая история.

✅  2023/06/02 04:00, alextretyak          #13 

Кстати, как Вы относитесь к автоматическим преобразованиям значений в булев тип и обратно? Типа 0 — «ложь», всё остальное — «истина».

Вот так, я считаю, и следует всегда писать: if int_var != 0 {...}, а просто if int_var {...} должно считаться ошибкой.

Ну с числами ещё ладно. Гораздо хуже когда например язык приводит автоматически строку к булевому типу: как следует приводить к bool пустую строку? В некоторых языках пустая строка считается истиной (Ruby, Crystal), а в некоторых — ложью (Python, JavaScript, PHP, Perl).

Если следовать утверждению "0 — «ложь», всё остальное — «истина»", то т.к. пустая строка не является нулём, то её следует считать истиной.

В общем, единственный разумный выход, как я вижу, это писать явно: например в 11l вместо if s {...} следует писать if s != ‘’ {...} либо if !s.empty {...}.

✅  2023/06/02 12:16, MihalNik          #14 

Кстати, в PHP 5 * '-', '-' * 5, 5 * "-" и "-" * 5 выдают одинаковый результат: 0. Везде разброд и шатания

Почему-то слабо типизированы C и PHP (в режиме по умолчанию, по крайней, мере), а разброд и шатания, получаются "везде". Странный вывод.

В языке могут отсутствовать строковые операции времени компиляции, но скорее всего есть строковые функции, которые при константных аргументах вычисляемы при компиляции. Например, функции str_repeat (PHP) и replicate (Clipper) тоже размножают строки.

Они слишком громоздки.

✅  2023/06/02 16:14, MihalNik          #15 

В том то и дело, что с точки зрения русского языка множится множимое. И занимается этим множитель.

Так и при чем тут порядок следования? Где строка, а где число в строго типизированном языке уже определено типом операндов.

Отличие записи a*b от b*a может приводить к ошибкам или бессмысленному отвлечению на сообщения компилятора, исправления.

Без оператора умножения его пришлось бы писать на Python так:

outfile.write("<table" + (' style="display: inline"' if is_inline else '') + ">\n")

Или так на C++:

outfile.write("<table"s + (is_inline ? " style="display: inline"" : '') + ">\n")
Вроде разница и небольшая, но читаемость кода с оператором умножения всё-таки немного лучше.

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

✅  2023/06/04 01:54, Автор сайта          #16 

компилятор позволяет писать всякие глупости вроде сложения, умножения или даже деления символьных литералов или символьных переменных

Это не всегда глупость. В криптографии какие только фокусы не выделываются над символами! Но это скорее исключение. В остальном можно лишь оправдать операции сложения и вычитания.

аналогично, ни один язык программирования не позволяет производить такие операции над указателями

Аналогично, с адресами тоже имеет смысл проводить операции сложения и вычитания. Остальным операциям тоже нет оправдания.

специальный тип для символа

Возможно, таких типов должно быть много: тип «ASCII», «utf-8», «cp-1251» и т. д. Но в правилах, которые описываются в этой статье, этого пока нет.

с тем, что в Rust отсутствует автоматическое приведение целых типов с меньшей разрядностью к целым типам с большей разрядностью, я не согласен

Подозреваю, что это оправдывается какими-то проблемами с выделением места под переменные. Но это лишь предположения.

Не совсем понятно, что вы имели в виду? Может «строка единичной длины эквивалентна символьному литералу»?

Я ж говорю, что в терминологии путаюсь. Всё обломилось смешалось в доме Смешальских Облонских. Так и есть, «… символьному литералу»

И о каких языках речь? Я что-то навскидку не могу припомнить такого поведения, хотя языков повидал немало.

Уже не помню. Кажется, какие-то потомки Лиспа. То ли Эйфория, то ли Ракета. То ли какой-то язык из этого списка.

можно добиться увеличения эффективности программиста без потери производительности. В наиболее общем виде можно сформулировать такое правило: если строковый литерал единичной длины можно рассматривать как символьный литерал ... тогда его нужно рассматривать именно как символьный литерал.

Тут есть ещё одна деталь. Символьный литерал — это скаляр. А строковый — нет. Разная семантика.

По причине того, что так захардкодено в компиляторе. :)(:

Придирчивый перфекционист заметит в этом общность подходов: в Си тоже символы захардкорены как числа.

вы мутите воду

Мне не нравится сама идея умножения числа и строки. «Ага, раз число не умножается на строку, то строку умножим на число и будем считать, что всё в ажуре».
outfile.write("<table" + ' style="display: inline"'*is_inline + ">\n")
Кстати, почему «*» (умножить), а не «&&» (логическое И) в стиле Си?

А тут строка умножается на значение логического типа. Как и в случае с числами, смешение мух и котлет. При этом предполагается, что «ложь» равна 0, а «истина» равна 1 (раз уж строка повторяется ровно 1 раз). Но на основании чего сделан вывод, что «истина» равна 1? Если инвертировать все биты числа (которые равны 0), то все биты станут равными 1, а это значение -1 (минус 1). Что должно получится при умножении строки на -1?

Истину логично превращать в 1

Машинная операция NOT 0 даст -1. То есть «не ноль» равно -1.

По моему мнению, да… числа не преобразуются автоматически в булев тип и обратно (и написать if (0) … или if (1) … в Java нельзя

Мне тоже не нравится автоматическое приведение числа к булеву типу. Но хочется отключить эмоции руководствоваться только здравым смыслом. Нельзя не признать, что с автоматическим приведением код становится короче. Краткость кода — один из признаков, который характеризует уровень языка: высокий он или низкий. Ведь чем больше деталей приходится описывать, тем меньший уровень абстракций. Не всегда опущение деталей полезно (например, в Фортране когда-то тип переменной определялся по первой букве её имени), но чаще всё-таки так.

Кстати, такая философская дилемма. Мы тут обсуждаем автоматические приведения типов, а ведь существует ещё странная (с точки зрения математики) практика применять побитовые операции к объектам целого типа. В математике нельзя применить, к примеру, операцию «исключающее или» к числу. А в Си — пожалуйста. По идее надо разделить числа и битовые поля: к первым надо применять арифметические операции, а ко вторым — побитовые. Правда, это тоже сделает код менее кратким.

Фигурировавший выше пример
"<table" + ' style="display: inline"'*is_inline + ">\n"
после переделки на мой вкус выглядел бы так :)
"<table" + (if is_inline; ' style="display: inline"') + ">\n"
Чуть-чуть длиннее, но без туманного умножения строки на булево значение.

✅  2023/06/04 09:04, kt          #17 

Мне тоже не нравится автоматическое приведение числа к булеву типу.

Опять выступлю в роли кулика на болоте PL/1. Давно придумано размещение переменных разных типов в одном месте памяти. Как раз для довольно частых случаев, когда в одном месте нужен, например, символ, а соседнем — он же, но уже как набор бит:
dcl s char, b bit(8) def(s);
здесь s и b "наложены" в памяти друг на друга. Мы очень часто используем этот прием и не путаемся с набором операций. Например, расчет CRC c использованием такого наложения. Компилятор только следит, чтобы объект большего размера не "наложили" на меньший. А все операции становятся обычными и законными для указанного типа объекта. И никаких преобразований типов!

✅  2023/06/04 14:22, Неслучайный читатель          #18 

Побитовые операции делают язык машинно-зависимым от двоичной системы счисления. Если вдруг воскреснет троичная «Сетунь», то реализация двоичной арифметики на ней будет громоздкой и неэффективной. Хотя на практике двоичная архитектура покрывает все 100% компьютеров. Ну может кроме квантовых.

s и b "наложены" в памяти друг на друга.

С одной стороны, технически просто и на первый взгляд даже элегантно. Но с другой стороны, у одного объекта появляются два владельца. В однопоточном коде это сойдёт с рук, а вот в многопоточном чревато неявными зависимостями.

✅  2023/06/04 15:20, kt          #19 

Нет никаких двух владельцев. Штернберг сравнивал этот механизм с двумя наклейками на одной и той же коробке. После компиляции имена заменяются на адреса в памяти. Поэтому всякие "мутексы" и семафоры в многопоточной задаче будут точно такие же, как и для обычных объектов. По сути многих задач один и тот же участок памяти надо представлять то так, то так. Но физически он остается один один.

✅  2023/06/04 20:36, Неслучайный читатель          #20 

Допустим, одному потоку передан char с именем s, а другому bit(8) с именем b. Первый поток постоянно что-то пишет в s. А второй об этом не знает: он однажды прочитал b и больше не читает, потому что уверен, что прочитанное значение b по-прежнему актуально. Хотя на самом деле постоянно меняется через s. Вот так возникают коллизии.

После компиляции имена заменяются на адреса в памяти.

Вот именно, что после компиляции. А при компиляции имеем два вроде бы разных объекта. Сишные объединения уже при компиляции дают понять, что это один и тот же объект. Наверно, при создании компилятора можно учесть, что это один и тот же объект. Но за счёт его усложнения.

Мьютексы, конечно, решают проблему, но её дешевле решить при компиляции.

✅  2023/06/04 22:45, kt          #21 

Я этого не понимаю. Потокам передается не "s" и не "b", а адрес, который выделил компилятор. Один и тот же адрес. Что касается объединений в Си, то "судить, об этом, можно всяко-разно, по-моему, так записной урод!" (с) "Гусарская баллада". И всего лишь класс памяти "defined" хорошо показывает, что это разное представление одного и того же элемента. Без введения новых понятий в языке. Это придумано 60 лет назад и мы использует 36 лет. И все в порядке. И не нужно применять булеву алгебру к числам. Как говорится, булево-булеву. При этом отсутствие изначально в Си битовых строк я считаю крупным промахом.

✅  2023/06/06 00:00, alextretyak          #22 

Машинная операция NOT 0 даст -1. То есть «не ноль» равно -1.

Машинной операции NOT соответствует сишный оператор ~, который для булевых переменных/выражений не используется — для них есть оператор !, и int(!0) даёт таки единичку.

а ведь существует ещё странная (с точки зрения математики) практика применять побитовые операции к объектам целого типа. В математике нельзя применить, к примеру, операцию «исключающее или» к числу. А в Си — пожалуйста.

Думаю, тут математика не причём, Си просто отображает машинные инструкции на операторы языка. Ну т.е. если целочисленные переменные соответствуют целочисленным регистрам процессора, то инструкции add соответствует оператор +, инструкции xor — оператор ^ и т.д. Не зря же Си иногда называют высокоуровневым ассемблером.

kt
Хотел задать вопрос на сайте pl1.su (т.к. здесь продолжать эту тему будет всё-таки неуместно), но комментарии там не работают. Если это ваш сайт, вы не могли бы это починить?

✅  2023/06/06 23:29, Автор сайта          #23 

и int(!0) даёт таки единичку

Так это плохо. Такое логическое «не» выливается в 3 машинных инструкции:
	cmp       dword ptr [ebp+8],0
sete al
and eax,1
Хотя есть прекрасное решение — сделай «истиной» значение -1, они с 0 как «инь» и «янь» взаимно зеркальны. Одно превращается в другое единственной операцией «NOT». Зачем множить неэффективность? А потом удивляемся распухшему коду. Тем более, что операции умножения строки на число всё равно не существует, в любом случае второй операнд будут сравнивать. Не всё ли равно, с чем сравнивать, с «1» или «-1»? А если с 0, то в противном случае вообще не важно, какой будет второй операнд.

математика не причём, Си просто отображает машинные инструкции на операторы языка.

Понятно, что отображает. Замеченных минусов такое отображение вроде не имеет, кроме переносимости на машины с другой системой счисления. Но до таких машин ещё дожить надо.

Си иногда называют высокоуровневым ассемблером

Он не «голубых кровей» и не знатного роду, но применим почти во всех нишах. А уж в системном программировании — № 1.

здесь продолжать эту тему будет всё-таки неуместно

А Вы в другом месте её продолжайте :)

Если это ваш сайт

Нет, сайт не его.

✅  2023/06/07 22:15, Автор сайта          #24 

здесь s и b "наложены" в памяти друг на друга


Кстати, о двух именах одного и того же объекта. Была когда-то мысль сделать логический тип с двумя именами: одно имя для «истина», другое — для «ложь». Например, есть логическая переменная «состояние включателя». Тогда обычно пишут:
 если состояние включателя
...
Но в словах «если состояние включателя» какая-то недосказанность. Вот так будет понятнее:
 если состояние включателя == истина
...
Но это длинновато. А теперь представим, что есть переменная с двумя именами:
 ВКЛ <-> ВЫКЛ = . . .	// объявление с инициализацией
Тогда код становится проще:
если ВКЛ
...
или
если ВЫКЛ
...
Главное — удачный подбор имён. Минусов тут три.
  • Не всегда прозрачно и очевидно, что изменение одного влечёт за собой изменение другого.
  • Что будет при передаче такой переменной в качестве параметра?
  • Как отследить передачу этой переменной в разные потоки с разными именами?

✅  2023/06/08 18:44, Автор сайта          #25 

Хочу спросить: а как в Вашем языке определяется длина строк? Эта величина хранится или строка заканчивается нулём?

✅  2023/06/08 23:45, Неслучайный читатель          #26 

dcl s char, b bit(8) def(s);

здесь s и b "наложены" в памяти друг на друга.

состояние включателя
ВКЛ <-> ВЫКЛ
Для большей наглядности и очевидности предлагаю подумать над таким вариантом:
object {char, bit(8)}		// переменная object имеет 2 типа
x = isdigit (object.char); // object ведёт себя как char
y = object.bit(8) & 0xff // object ведёт себя как bit(8)

состояние включателя {ВКЛ, ВЫКЛ}
если состояние включателя.ВКЛ
. . .
Тут больше ясности, что к чему относится. Над синтаксисом, конечно, надо думать.

✅  2023/06/10 00:00, alextretyak          #27 

Хочу спросить: а как в Вашем языке определяется длина строк? Эта величина хранится или строка заканчивается нулём?

В текущей реализации класс строк основан на std::u16string, который хранит длину строки, и одновременно хранимая строка завершается нулём. Такое поведение присуще если не всем реализациям STL, то большинству, т.к. это повышает производительность метода c_str(), который по сути становится эквивалентен методу data(). Длину хранить нужно обязательно — это залог эффективной работы со строками, включая быструю проверку выхода индекса символа за границу строки в операторе [], конкатенацию строк, получение последнего символа строки или функции вроде ends_with. А завершающий ноль полезен для вызова функций WinAPI, которым требуется передать строку в качестве аргумента.

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

Описание языка

●  Философия языка

●  Правила языка: алфавит

●  Правила языка: строки, комментарии

●  Правила языка: скобки и их согласование

●  Правила языка: идентификаторы

●●  Правила языка: встроенные типы

●  Правила языка: числовые типы и числовые константы

●  Правила языка: строковые литералы

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/12/07 00:00 ••• alextretyak
Переключатель

2024/12/06 18:44 ••• Анкнав
Русский язык и программирование

2024/12/01 00:00 ••• alextretyak
Продолжение цикла и выход из него

2024/11/29 23:08 ••• Вежливый Лис
О русском ассемблере

2024/11/26 23:53 ••• Бурановский дедушка
ЕС ЭВМ — это измена, трусость и обман?

2024/11/25 18:31 ••• Деньги на WWWетер
Ресурсы, посвящённые созданию языков программирования и компиляторов

2024/11/12 20:24 ••• Вежливый Лис
Правила языка: строки, комментарии

2024/11/12 13:10 ••• Вежливый Лис
Новости и прочее

2024/11/12 00:32 ••• Автор сайта
Оценка надёжности функции с несколькими реализациями

2024/11/06 02:50 ••• Иван
Энтузиасты-разработчики компиляторов и их проекты

2024/11/05 23:51 ••• Борис К.
Изменение приоритетов операций

2024/11/05 23:38 ••• Борис К.
Шестнадцатиричные и двоичные константы

2024/11/01 12:11 ••• ИванАс
Русской операционной системой должна стать ReactOS