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

Продолжение цикла и выход из него

В циклах иногда иногда бывает удобным проверить некое условие и при его выполнении выйти из цикла.
Goto must die
В Си-подобных языках для этой цели служит оператор «break». Однако бывают такие ситуации, когда условие выхода удобнее проверить не в заголовке цикла, а «по месту требования».

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

for (int i=0; i < Imax; ++i)
{
   // ...
   int  надо_выйти_из_цикла = FALSE;
   for (int j=0; i < Jmax; ++j)
   {
       // ...
       if (условие)
       {  надо_выйти_из_цикла = TRUE;
          break;
       }
       // ...
   }
   if (надо_выйти_из_цикла)
       break;
   // ...
}
        Не очень красивое решение. Вот поэтому подобные случаи являются оправданием «goto»:
for (int i=0; i < Imax; ++i)
{
   // ...
   for (int j=0; i < Jmax; ++j)
   {
       // ...
       if (условие)
          goto  end_loop;
       // ...
   }
   // ...
}
end_loop:;
        Это короче, элегантнее, но... с «goto»! Это тот самый «goto», который стал причиной многих психических заболеваний, банкротств и падений индекса NASDAQ. А нельзя ли так же кратко, но без любимого врага Дейкстры, о котором мы поговорим в следующей статье? Может, в других языках есть приёмы получше?

Java

В Java для избавления от «goto» применяется техника именованных блоков.
LOOP: 
for(int i=0; i < Imax; ++i)
{
   // ...
   for(int j=0; j < Jmax; ++j)
   {
      /// ...
      if (условие) 
          break LOOP;
      // ...
   }
   // ...
}
Хотя значительно нагляднее было бы так:
for(int i=0; i < Imax; ++i)
{
   // ...
   for(int j=0; j < Jmax; ++j)
   {
      /// ...
      if (условие) 
          break END_LOOP;
      // ...
   }
   // ...
} :END_LOOP; 
Есть сильное подозрение, что так не сделали только потому, что это очень смахивает на простую замену ключевого слова «goto» на «break».

PHP

         В PHP выход из вложенного цикла выглядит, на мой взгляд, значительно элегантнее. После «break» указывается количество вложенных циклов, которые должен «покинуть» оператор «break». В приведённом примере, который аналогичен приведённому выше для Java, «break» должен «пересечь» две фигурные скобки «}», чтобы оказаться за пределами двух циклов.
for($i=0; $i < $Imax; ++$i)
{
   // ...
   for($j=0; $j < $Jmax; ++$j)
   {
      // ...
      if(условие) 
          break 2;
      // ...
   }
   // ...
}
Разыскивается убийца GOTO


        Такое усовершенствование пришло в голову автору этих строк задолго до знакомства с PHP. Такое «параллельное изобретение» подверждает невозможность полной монополии на хорошие идеи. Но поскольку имелись идеи не только насчёт «break N», то при знакомстве PHP обратил внимание, что в нём нет «continue N». Идея, которая вполне «симметрична» предыдущей. Хотя для применения «continue N» не очень легко привести пример из жизни для иллюстрации его полезности. Возможно, читатели этих строк предложат что-то в комментариях к статье.

Итог



        И так, теперь нам необходимо найденные находки изложить в систематизированном виде в сочетании с нашим «симметричном скобочном» стиле:
(int i=0 loop i < $Imax; ++i
   // ...
   (int j=0 loop j < $Jmax; ++j
      // ...
      (if  условие
          exit 2)
      // ... 
   )
   // ...
}
        Замена ключевого слова «break» на «exit», как видится, больше соответствует смыслу выполняемого действия. Но это для тех, кто предпочитает программировать с использованием английских идентификаторов. Для приверженцев повсеместного употребления русской речи ключевое слово одно — «выход». Ну и теперь, конечно, было бы интересно узнать, какой графический (вместо синтаксического) сахар должна предлжить программисту IDE. Было бы логичным увидеть примерно такое:

  // Объемлющий цикл  
 
(       инициал.выр-я loop условие; выр-я в конце цикла  
  // Выход из объемлющего цикла  
 
(       if условие  
                           exit
 
  // Вложенный цикл  
 
(       инициал.выр-я loop условие; выр.в конце цикла  
  // Продолжение вложенного цикла
(       if условие  
                               continue )
 
     
  // Выход из вложенного цикла
(       if условие  
                                                 exit 2 )
 
  // Операторы цикла
 
  // В программе на С здесь бы поставили метку типа «end_loop:;»


        Получается очень наглядно. Стрелки от ключевого слова «exit» показывает, из какого цикла будет произведён выход. Если же «exit» имеет числовой операнд, то стрелка должна пересечь границы нескольких циклов. Величина операнда равна числу покидаемых циклов. Оператор «continue» сопровождается другого рода стрелкой, вид которой напоминает о возврате к началу цикла.

        Ещё раз хочется отметить, что графическое оформление приведённых выше примеров лишь иллюстрирует идею, но не является ни единственным, ни окончательным вариантом. Значки, стрелки и прочие графические элементы могут и будут иметь другой вид, а пока что показывают сам принцип тексто-графической композиции текста программы.

Опубликовано: 2012.09.25, последняя правка: 2024.11.21    22:31

ОценитеОценки посетителей
   ███████████████████████ 28 (52.8%)
   █████████ 11 (20.7%)
   ████ 5 (9.43%)
   ████████ 9 (16.9%)

Отзывы

✅  2013/05/07 16:37, koba          #0 

Вообще говоря, выход из глубоко вложенного цикла за пределы внешнего цикла является единственным оправданием любителей goto. В действительности такие любители не понимают, а их оппоненты не показывают (может быть, тоже не понимают), одного обстоятельства, которое показывает их слабое понимание принципов программирования. Посмотрим, в чём смысл нескольких вложенных циклов, из которых из самого внутреннего приходится делать выход? А смысл очень простой: проверяется некоторое множество абсолютно равноценных элементов, но это множество программист по тем или иным причинам разрезал на несколько измерений. То есть он некие элементы представил как неравноценные, но при этом хочет обрабатывать их как равноценные. Разумеется, это противоречие такие программисты не только не могут разрешить, но даже не знают о его существовании. То есть они сканирование множества в целом заменяют сканированием отдельных измерений. Поэтому им приходится использовать такой несуразный приём как выпрыгивание наружу.

В действительности, когда программист осознает, что он превратил равноценные элементы в неравноценные, сразу возникает решение с одним единственным циклом -- проходом целиком по всему множеству равноценных элементов.

Пример

Было с goto и вложенными циклами так:
int matrix[n][m];
int value;
...
for(int i=0; i<n; i++)
for (int j=0; j<m; j++)
if (matrix[i][j] == value) {
printf("value %d found in cell (%d,%d)\n",value,i,j);
goto end_loop;
}
printf("value %d not found\n",value);
end_loop: ;
Стало без goto и с одним циклом так (пример на C):
int matrix[n][m];
int value;
...
int i = 0;
int j = 0;
int f;

while (!((f = (matrix[i][j] == value)) !! (i == n) && (j == m)))
if (!(j = ++j % m)) i = ++i % n;

if (f) printf("value %d found in cell (%d,%d)\n",value,i,j);
else printf("value %d not found\n",value);

✅  2013/05/07 22:13, Автор сайта          #1 

Ну давайте представим, что у нас есть массив ссылок, который мы перебираем в объемлющем цикле. Элементы этого массива ссылаются, скажем, на ещё какие-то массивы неодинаковой длины. Эти массивы мы будем перебирать во внутреннем цикле. Не правда ли, это логично? И если перед нами стоит задача поиска, то найдя нужный элемент, мы смело даём команду «exit 2» и выходим обоих циклов. Заурядная задача, заурядное решение, но кратчайшим способом.

✅  2014/01/31 10:40, Pensulo          #2 

Пример решения предложенной Вами задачки на VisualBasic с применением всего одного цикла:
Const iMax As Integer = 100
Private Source(1 To iMax) As Variant
' Source — одномерный массив состоящий из ссылок
' на другие одномерные массивы переменной длинны.
Function SearchInArray(Required As String) As Boolean
Dim i As Integer: Dim j As Integer
  i = 1: SearchInArray = False
  Do
    j = LBound(Source(i)) ' Получить начальный индекс переменного массива
    If Source(i)(j) = Required Then
      SearchInArray = True
      Exit Do  ' В данном примере можно сразу употребить 'Exit Function'
    End If
    j = j + 1
    If j > UBound(Source(i)) Then
  ' Анализировать выход за конечный индекс переменного массива
      i = i + 1
    End If
  Loop Until i > iMax
End Function
Аналогично эту задачу можно решить на любом ЯВУ без употребления оператора выхода из объемлющего (внешнего) цикла.

Решение с двумя циклами привожу следом. Отмечу при этом, что и в этом случае не потребовался оператор выхода из объемлющего (внешнего) цикла:
Function SearchInArray2(Required As String) As Boolean
Dim i As Integer: Dim j As Integer
  i = 1: SearchInArray2 = False
  Do
    j = LBound(Source(i))
    Do
      If Source(i)(j) = Required Then
        SearchInArray2 = True
        Exit Do 'Опять же в данном примере можно сразу употребить 'Exit Function'
      End If
      j = j + 1
    Loop Until j > UBound(Source(i))
    If SearchInArray2 = True Then
    ' Этот анализ можно опустить в случае использования 'Exit Function' ранее
      Exit Do
    End If
    i = i + 1
  Loop Until i > iMax
End Function

✅  2014/01/31 16:13, Автор сайта          #3 

Приведённый Вами пример использует дополнительные переменные, чтобы указать внешнему циклу — продолжать его или нет. Т.е. заведомо более длинное решение.

✅  2014/02/25 15:49, Руслан          #4 

Более короткое далеко не всегда лучше чем более длинное решение. В любом случае решение по феншую будет лучше.

✅  2014/07/02 05:56, utkin          #5 

Получается очень наглядно. Стрелки от ключевого слова «exit» показывает, из какого цикла будет произведён выход. Если же «exit» имеет числовой операнд, то стрелка должна пересечь границы нескольких циклов. Величина операнда равна числу покидаемых циклов. Оператор «continue» сопровождается другого рода стрелкой, вид которой напоминает о возврате к началу цикла.

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

✅  2014/07/02 13:37, Автор сайта          #6 

В IDE должны быть настройки, которые включают или отключают показ тех или иных графических элементов, например: «сопровождать стрелкой оператор выхода», «сопровождать условные операторы значками» и т.д. Тогда каждый настроит себе по вкусу.

✅  2014/11/07 11:30, Сергей          #7 

2 koba Сокращение уровня вложенности циклов («уплощение») является чисто техническим приёмом, который в данной ситуации имеет только одну цель — избежать выхода из глубоко вложенного цикла. Однако результат достигается за счёт потери наглядности и понятности кода. Использование «exit 2» в данной ситуации не несёт никаких вредных последствий, полностью сохраняет эффективность кода и, самое важное, не причиняет вреда лёгкости чтения и понимания кода.

✅  2014/11/27 09:06, enburk          #8 

+1
Даже вариант с goto читается мгновенно. В исправленный же вариант приходится вглядываться и размышлять, что замыслил автор...

Предложил бы ещё вместо «exit 2» писать «exit exit».

✅  2014/11/27 17:41, Автор сайта          #9 

Тоже вариант. Вот только при обходе многомерного массива в цикле нужно будет писать «exit exit ... exit».

✅  2015/11/02 01:04, Nick          #10 

2 Сергей
+1

2 coba
но если уж на то пошло, то для С можно было и попроще написать:
int matrix[n][m];
int value;
int f;

for(int i = n * m; --i >= 0 && f = (matrix[i] != value););

if (!f) printf("value %d found in cell (%d,%d)\n",value,i,j);
else printf("value %d not found\n",value);
И вообще все эти continue, break и т.д. просто синтаксический сахар.
continue легко заменяется оператором if() {}. break не совсем конечно сахар, т.к. для замены дополнительная переменная нужна, но если бы goto не злоупотребляли, то и break не понадобился бы... (но "бы" мешает)
P.S. Даже эта запись
int matrix[n][m];
— синтаксический сахар. Но если из языков убрать этот сахар, будет ассемблер.

✅  2015/11/08 19:28, Автор сайта          #11 

Двухмерные матрицы — самый простой способ показать случай, когда «goto» полезен, потому что короче. При отсутствии иных вариантов, конечно. Но «break N» элегантно устраняет этот наверное единственный обоснованный случай применения «goto» — не правда ли? А для работы с элементами множеств лучше использовать цикл «foreach».

✅  2016/07/03 13:36, rst256          #12 

Нет не правда, там не нужен goto? Как и не нужен лишний цикл, хороший правильный вариант тут уже продемонстрировали.
А вот первый попавший под руку код с goto:
static size_t utf8_decode(const char *s, const char *e, unsigned *pch) {
unsigned ch;

if (s >= e) {
*pch = 0;
return 0;
}

ch = (unsigned char)s[0];
if (ch < 0xC0) goto fallback;
if (ch < 0xE0) {
if (s+1 >= e !! (s[1] & 0xC0) != 0x80)
goto fallback;
*pch = ((ch & 0x1F) << 6) !
(s[1] & 0x3F);
return 2;
}
if (ch < 0xF0) {
if (s+2 >= e !! (s[1] & 0xC0) != 0x80
!! (s[2] & 0xC0) != 0x80)
goto fallback;
*pch = ((ch & 0x0F) << 12) !
((s[1] & 0x3F) << 6) !
(s[2] & 0x3F);
return 3;
}
{
int count = 0; /* to count number of continuation bytes */
unsigned res = 0;
while ((ch & 0x40) != 0) { /* still have continuation bytes? */
int cc = (unsigned char)s[++count];
if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
goto fallback; /* invalid byte sequence, fallback */
res = (res << 6) ! (cc & 0x3F); /* add lower 6 bits from cont. byte */
ch <<= 1; /* to test next bit */
}
if (count > 5)
goto fallback; /* invalid byte sequence */
res != ((ch & 0x7F) << (count * 5)); /* add first byte */
*pch = res;
return count+1;
}

fallback:
*pch = ch;
return 1;
}
Если код под fallback кажется вам достаточно небольшим, чтобы расплодить его заместо вызова goto, то представьте в добавок нему ещё пару сотен строк, сути это не поменяет

✅  2016/07/03 15:36, Автор сайта          #13 

static size_t utf8_decode(const char *s, const char *e, unsigned *pch) {
unsigned ch;

if (s >= e) {
*pch = 0;
return 0;
}

ch = (unsigned char)s[0];
if (ch < 0xC0) fallback(*pch, ch); return 1;
if (ch < 0xE0) {
if (s+1 >= e !! (s[1] & 0xC0) != 0x80)
fallback(*pch, ch); return 1;
*pch = ((ch & 0x1F) << 6) !
(s[1] & 0x3F);
return 2;
}
if (ch < 0xF0) {
if (s+2 >= e !! (s[1] & 0xC0) != 0x80
!! (s[2] & 0xC0) != 0x80)
fallback(*pch, ch); return 1;
*pch = ((ch & 0x0F) << 12) !
((s[1] & 0x3F) << 6) !
(s[2] & 0x3F);
return 3;
}
{
int count = 0; /* to count number of continuation bytes */
unsigned res = 0;
while ((ch & 0x40) != 0) { /* still have continuation bytes? */
int cc = (unsigned char)s[++count];
if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
fallback(*pch, ch); return 1;/* invalid byte sequence, fallback */
res = (res << 6) ! (cc & 0x3F); /* add lower 6 bits from cont. byte */
ch <<= 1; /* to test next bit */
}
if (count > 5)
fallback(*pch, ch); return 1; /* invalid byte sequence */
res != ((ch & 0x7F) << (count * 5)); /* add first byte */
*pch = res;
return count+1;
}
}
Так сойдёт? Но если бы из функции можно было возвращить несколько значений, то «return 1, pch» смотрелось бы красивее.

✅  2016/08/10 18:55, rst256          #14 

Так сойдёт? Но если бы из функции можно было возвращить несколько значений, то «return 1, pch» смотрелось бы красивее.

Наверное да. Я по, крайней мере, поступил именно так при адаптации данного кода под лексер, у вас ведь тоже, как я понимаю, fallback(*pch, ch) — это макрос?

✅  2017/09/19 13:27, Comdiv          #15 

Многие видели заголовок статьи Дейкстры, но немногие её читали. Дейкстра возражает против непоследовательных процессов, а goto — всего лишь способ нарушения, который присутствовал в языках того времени. Теперь наплодили много специализированных аналогов goto для частных случаев, посчитав, что они решили проблему, но несмотря на то, что такие ограниченные goto лучше, чем полноценный goto, это не решает проблему принципиально. Там, где критически важна корректность кода, неструктурные выходы не практикуются. Примером может служить MISRA C, являющаяся стандартом де факто для встраиваемых систем в автомобилях. В этих требованиях неструктурные переходы запрещены почти полностью, включая преждевременный return. Исключение сделано для единственного break на цикл.

✅  2017/09/18 22:36, Автор сайта          #16 

Я задавал вопрос Андрею Карпову, разработчику статического анализатора кода PVS-Studio, как влияет на качество работы анализатора оператор «goto», а так же «break», «continue» и «return». Ответил одним словом: «Плохо». Правда, непонятно, отнёс он это только к «goto», или к другим операторам тоже. Обещал написать статью на эту тему, но что-то не идёт к нему вдохновение...

✅  2018/06/24 21:39, rst256          #17 

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

✅  2018/06/25 15:10, Автор сайта          #18 

Спору нет, без мозга — никуда. Да не простого, а обременённого соответствующими знаниями. Но мозг может устать, потерять внимательность, не совладать с объёмом информации, который надо держать в голове одновременно. И тут приходят на помощь вспомогательные инструменты. Почитаешь о том, какие ошибки находят анализатора кода, и думаешь: «Нет, в таком частоколе букв, цифр и знаков я бы искал ошибку до поседения». Всему своё место: мозгу — творческая работа, инструментам — рутина. Ну и язык должен предостерегать, по возможности, от ошибок.

✅  2024/10/23 19:07, bvz          #19 

Такое чувство, что проблема искусственно создана на ровном месте. Специально, чтобы потом триумфально разрешить её с помощью оператора goto. Кроме циклов for, между прочим, существуют также ещё и циклы while и until. Что мешает использовать один из них? Пусть условие надо_выйти_из_цикла с отрицанием добавлено через конъюнкцию в самый внешний цикл while. И тогда на каждом шаге этого самого внешнего цикла будет проверяться истинность условия надо_выйти_из_цикла.

Собственно говоря, циклы for, по-моему, придумали с одной единственной целью: избавиться от записи i=i+1 внутри цикла и включить её в заголовок. В некоторых случаях это, может, и удобно. Но если в данном конкретном случае цикл for не подходит, зачем нужно насильно его использовать?

✅  2024/10/23 20:07, Автор сайта          #20 

Не знаю, искусственным ли путём образовалась проблема или естественным. Однако до сих пор для выхода из нескольких циклов используют либо «goto»
цикл <проверка условия>
цикл <проверка условия>
если <дополнительная проверка условия>
goto <метка>
// конец второго цикла
// конец первого цикла
<метка>:
либо заменитель «goto» («break» с меткой)
цикл <проверка условия>
цикл <проверка условия>
если <дополнительная проверка условия>
break <метка>
// конец второго цикла
// конец первого цикла
<метка>:
либо пользуются самым длинным вариантом — создают дополнительные переменные с дополнительными танцами с бубнами:
надо_выйти_из_цикла = ложь
цикл <проверка условия>
цикл <проверка условия>
если <дополнительная проверка условия>
надо_выйти_из_цикла = истина
break
// конец второго цикла
если надо_выйти_из_цикла
break
// конец первого цикла
<метка>:
Независимо от вида цикла: «while», «until», «for».
чтобы потом триумфально разрешить её с помощью оператора goto.
Вы невнимательно читали. Как раз-то этот вариант
цикл <проверка условия>
цикл <проверка условия>
если <дополнительная проверка условия>
break N // N -число циклов, которые надо покинуть
и самый лаконичный, и самый прозрачный. Для него не нужны ни метки (как для «goto» и «break» с меткой), ни дополнительные переменные.
циклы for, по-моему, придумали с одной единственной целью: избавиться от записи i=i+1 внутри цикла
Просто «for» — наиболее обобщённый вид цикла, в нём и инициализация переменной цикла, и действие в конце. Правда, он не может заменить «do...while».

✅  2024/10/24 00:43, bvz          #21 

Автор сайта, ладно, я понял вашу мысль. Лаконичный — это значит, краткий, правильно? Но ведь не всегда краткий = изящный. Изящность, как и красота — вещь довольно субъективная. На мой взгляд, изящно было бы писать программу, придерживаясь один раз принятых принципов. Например, не использовать оператор goto. И никогда не отступая от них. Но вы и не использовали, вы использовали break N.

Короче, я бы всё равно писал по-своему. Я только не понял, предлагал ли кто-нибудь выше мой вариант. А что касается искусственности созданной проблемы — проблема эта возникает в тот момент, когда "бывают такие ситуации, когда условие выхода удобнее проверить не в заголовке цикла, а «по месту требования»." Так вот, я бы просто поместил это условие именно в заголовок каждого цикла. Руки бы у меня от этого не отвалились. Удобство или неудобство может возникнуть на стадии понимания программы. Но опять-таки, не думаю, что такую программу будет намного труднее понять.

И более того. Если вы всегда записываете условие выхода в заголовок цикла, все такие программы будут выглядеть одинаково. Вне зависимости от количества вложенных циклов. Соответственно, исчезает возможность ошибиться на стадии break N. Например, написать break 6 вместо break 5. А может, и вовсе стоит написать рекурсивную процедуру для неограниченного числа вложенных "циклов".

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

✅  2024/10/24 23:24, Автор сайта          #22 

не понял, предлагал ли кто-нибудь выше мой вариант.
Как минимум, в теоретических работах по программированию он рассматривался. Наверно, не ошибусь, если скажу, что во многих языках функционального программирования цикл заменили рекурсией и в таком случае условия продолжения (или прекращения) цикла собраны в одном месте.
исчезает возможность ошибиться на стадии break N. Например, написать break 6 вместо break 5.
Очень мало возможностей ошибиться. Во-первых, если циклов 5, то «break 6» выдаст ошибку при компиляции. Во-вторых, при программировании ступеньками очень наглядно видно, на каком уровне справа находится «break 6».
Если вы всегда записываете условие выхода в заголовок цикла, все такие программы будут выглядеть одинаково.
Вопрос дискуссионный.
такие алгоритмы, наверное, не будут эквивалентными.
Да, подумайте, можно ли цикл с проверкой в конце («do...while») заменить удобным способом на цикл с проверкой вначале. В теории можно всё, но удобно ли это? Циклы можно заменить эквивалентными рекурсиями, но станут ли от этого программы проще и понятнее — тот ещё вопрос.

✅  2024/10/26 00:00, alextretyak          #23 

Так вот, я бы просто поместил это условие именно в заголовок каждого цикла.
Не во всех языках программирования в заголовок цикла for можно вставить дополнительное условие.
Например, в том же Python нет универсального цикла for. И для выхода из нескольких циклов приходится, как уже было сказано выше «создавать дополнительные переменные с дополнительными танцами с бубнами».

Вот конкретный пример. У меня есть Python-скрипт, которому требуется определить директорию с установленной Microsoft Visual Studio, причём более новые версии должны быть в приоритете. Для этого я использовал такой вложенный цикл:
for version in ['2022', '2019', '2017', '2015', '2013']:
for edition in ['BuildTools', 'Community', 'Enterprise', 'Professional']:
if os.path.isfile('C:\Program Files (x86)\Microsoft Visual Studio\' + version
+ '\' + edition + R'\VC\Auxiliary\Build\vcvarsall.bat'):
... # здесь нужно выйти из всех циклов
Но недавно этот скрипт пришлось поправить, так как оказалось, что 2022-ая студия может быть установлена не в "C:\Program Files (x86)", а в "C:\Program Files". Вот ссылка на мой коммит с этой правкой: https://github.com/symasm/symasm/commit/7d2f432a9ef45784646a01662be874abb8ba17e7. В итоге код получился вот таким:
was_break = False
for version in ['2022', '2019', '2017', '2015', '2013']:
for edition in ['BuildTools', 'Community', 'Enterprise', 'Professional']:
for x86 in [0, 1]:
vcvarsall = 'C:\Program Files' + ' (x86)'*x86 + '\Microsoft Visual Studio\' + version + '\' + edition + R'\VC\Auxiliary\Build\vcvarsall.bat'
if os.path.isfile(vcvarsall):
was_break = True
#print('Using ' + version + '\' + edition)
break
if was_break:
break
if was_break:
break
if was_break:
masm_pathname = subprocess.check_output('"' + vcvarsall + '" x64 > nul && where ml64', encoding = 'ascii').rstrip()
open('masm_pathname.txt', 'w').write(masm_pathname)
else:
print('''Unable to find vcvarsall.bat!
If you do not have Visual Studio 2013, 2015, 2017, 2019 or 2022 installed please install it or Build Tools for Visual Studio from here[https://visualstudio.microsoft.com/downloads/].''')
masm_pathname = '-'
Видите замечательную лесенку из if was_break? И теперь представьте, что вам запросто может потребоваться добавить ещё один вложенный цикл for. А если использовать язык программирования с возможностью выхода из нескольких циклов, например 11l, то код получится значительно проще:
loop(version) [‘2022’, ‘2019’, ‘2017’, ‘2015’, ‘2013’]
loop(edition) [‘BuildTools’, ‘Community’, ‘Enterprise’, ‘Professional’]
loop(x86) [0, 1]
var vcvarsall = ‘C:\Program Files’(‘ (x86)’*x86)‘\Microsoft Visual Studio\’version‘\’edition‘\VC\Auxiliary\Build\vcvarsall.bat’
if fs:is_file(vcvarsall)
//print(‘Using ’version‘\’edition)
masm_pathname = os:(‘"’vcvarsall‘" x64 > nul && where ml64’).stdout.rtrim("\n")
File(‘masm_pathname.txt’, WRITE).write(masm_pathname)
loop(version).break

loop.was_no_break
print(‘Unable to find vcvarsall.bat!
If you do not have Visual Studio 2013, 2015, 2017, 2019 or 2022 installed please install it or Build Tools for Visual Studio from here[https://visualstudio.microsoft.com/downloads/].’)
masm_pathname = ‘-’
Конструкция loop(version).break осуществляет выход из соответствующего цикла (в языке 11l меток нет). Вместо неё можно использовать эквивалентную (при данном количестве вложенных циклов!) конструкцию: ^^loop.break. Я выбрал символы крышечки (^) вместо числа после break, так как:
  1. не очевидно с какого числа начинается отсчёт, с 0 или с 1 (в PHP начинается с 1, т.е. просто break равнозначен break 1, а чтобы прервать внешний цикл, нужно использовать break 2, но в программировании нумерацию принято начинать с 0, т.е. было бы логично, что просто break равнозначен break 0... в общем, возможна путаница; с крышечками же логика проще — если их нет, то loop.break он и в Африке... осуществляет выход только из текущего цикла, если одна крышечка, то выходим из вышестоящего цикла по отношению к текущему, если крышечки две, то идём ещё выше (сам этот символ символизирует направление вверх, потому он и был выбран) и т.д.);
  2. число после break создаёт ощущение, что вместо него можно подставить какое-то целочисленное выражение и написать например break 2+3 или вообще вставить какую-нибудь переменную:
loop ...
loop ...
loop ...
int entered_number = int(input(‘Сколько циклов вы хотите покинуть?’))
break entered_number
Более «монолитно», чем break 2, выглядит запись break_2, но так сделать в языке программирования не получится, т.к. break_2 может быть действительным именем переменной/идентификатором.

Но если вернуться к исходной проблеме — лесенке из if was_break, то есть и другое решение — поместить необходимую логику внутрь функции, которая вместо break будет делать return. Но мне не нравится такое решение — нужно придумывать имя для новой функции, которая используется только в одном месте и только для обхода ограничения используемого языка программирования. Похоже на костыль.
Хотя для применения «continue N» не очень легко привести пример из жизни для иллюстрации его полезности. Возможно, читатели этих строк предложат что-то в комментариях к статье.
Предложат-предложат. 🤣 Вот два наиболее интересных встретившихся мне примера, в которых было бы оправдано применение «continue N»:
  1. В транспайлере Python → 11l вот эти 4 строки (https://github.com/ 11l-lang/ python_to_11l/ blob/ f54b0475ebbceaea280e1b9eca6eab99069f2f4f/parse.py#L3053-L3056) не самого понятного Python-кода можно заменить одной строкой ^loop.continue (аналог continue 2).
  2. Этот код на языке D (https://rosettacode.org/wiki/Peaceful_chess_queen_armies#D) использует continue <метка>. И, как видно по этой же веб-странице, эту конструкцию поддерживают также языки Go, Java, Kotlin, Swift. Но т.к. Python такого не поддерживает, пришлось усложнить логику путём добавления двух else-веток для for. Вот этот перевод на Python: https://github.com/11l-lang/_11l_to_cpp/blob/master/tests/python_to_cpp/Rosetta%20Code/p.txt#L2272).
Там же на rosettacode.org можно поискать ещё хорошие примеры употребления continue <метка> (вдруг я что-то упустил).

P.S. В программном коде в сообщении двойные обратные слэши (внутри строковых литералов и не только) почему-то заменяются на одиночные. Если будет время, посмотрите в чём проблема. Корректный код на Python из данного сообщения доступен в копии этого сообщения у меня [на сайте](https://alextretyak.ru/comm/compiler.su/prodolzhenie-tsikla-i-vykhod-iz-nego).

✅  2024/10/26 16:38, Автор сайта          #24 

^^loop.break
Я префиксному «^» собираюсь назначить иную роль. Один такой символ — увеличение области видимости на уровень выше. То есть за пределы скобки «)»:
( (^переменная = ... // инициализация переменной с выводом её типа
... = переменная // внутри скобок она видна
)
... = переменная // за пределами первой пары скобок тоже видна
)
... = переменная // за пределами второй пары скобок не видна
не очевидно с какого числа начинается отсчёт, с 0 или с 1
Число после «break» — это сколько циклов надо покинуть. Если 0, то значит цикл покидать не надо.
число после break создаёт ощущение, что вместо него можно подставить какое-то целочисленное выражение и написать например break 2+3")
Компилятор разочарует своими сообщениями об ошибках. Так же, как если бы вместо var4 написали бы var2*2.
Эти 4 строки . . . не самого понятного Python-кода можно заменить одной строкой:
^loop.continue
👍

✅  2024/10/26 16:49, ИванАс          #25 

for version in ['2022', '2019', '2017', '2015', '2013']:
for edition in ['BuildTools', 'Community', 'Enterprise', 'Professional']:
for x86 in [0, 1]:
Наверное, можно заменить на product из itertools.

✅  2024/10/28 00:00, alextretyak          #26 

Я префиксному «^» собираюсь назначить иную роль. Один такой символ — увеличение области видимости на уровень выше.
Так одно другому не мешает.
В 11l, кстати, планируется аналогичный функционал: использовать префиксы ^ для доступа к переменным из внешней области видимости. Особенно это может быть полезно во время отладки (например, есть цикл по i, внутри него ещё какой-то цикл, внутри которого ещё маленький цикл по i, находясь в котором хочется получить текущее значение переменной i верхнего уровня, это можно сделать посредством записи ^i). Я об этом писал ещё в статье «Каркас нового языка программирования» на Хабре и в комментариях там по поводу этой возможности велись горячие споры: https://habr.com/ru/articles/350694/comments/#comment_10701298.
Также хочу обратить внимание на то, что в записи ^^loop.break крышечки относятся не к break, а к loop. Т.е. имеется в виду (‍^^loop).break, а не ^^(loop.break).
( (^переменная = ... // инициализация переменной с выводом её типа
... = переменная // внутри скобок она видна
)
... = переменная // за пределами первой пары скобок тоже видна
)
Ну в чистом виде такой код малополезен — проще создать переменную чуть раньше за скобкой. А на практике может понадобиться создать «внешнюю» переменную в теле условного оператора. Вот только вопрос: что делать в случае, когда условие не выполняется — как должно отрабатывать обращение к этой переменной в последующем коде?

И разве ваш язык не предполагает явное обозначение объявления новой переменной?
В 11l обязательно указывать при инициализации, что объявляется новая переменная, с помощью ключевого слова var\пер:
если <условие>
пер имя_новой_переменной1 = ... // видна только внутри тела условного оператора
пер ^имя_новой_переменной2 = ... // видна также снаружи,
вывод(имя_новой_переменной2) // но что делать, если <условие> ложно?
Также префикс ^ можно будет использовать для обозначения «внешних» счётчиков цикла:
цикл(^н) 0 .< 100 // цикл от 0 до 100, включая 0, но не включая 100
если <условие>
цикл.прервать // или цикл.выход

вывод(н) // выводим значение `н` на итерации, в которой цикл был прерван
// если цикл не был прерван, то в `н` будет число 100
И ещё — для внешних return:
фн внешняя_функция(...)
фн локальная_функция()
^возврат // возврат из внешней функции
локальная_функция()
... // этот код выполняться не будет, т.к. внутри вызова
... // локальной функции осуществляется возврат из внешней
Число после «break» — это сколько циклов надо покинуть. Если 0, то значит цикл покидать не надо.
А вот разработчики языка PHP с вами не согласны.
В версии до 5.4 break 0 работал так же как break 1, а в 5.4 и более новых версиях — писать break 0 вообще запретили.
[https://php.ru/manual/control-structures.break.html]
Компилятор разочарует своими сообщениями об ошибках. Так же, как если бы вместо var4 написали бы var2*2.
Эмм. Не понял, а в чём ошибка компиляции в записи var2*2? Это же простое умножение значения переменной var2 на число 2.

✅  2024/11/04 00:23, Автор сайта          #27 

alextretyak, наконец-то отвечаю Вам.
В 11l, кстати
Кстати, что означает такое название языка? Почему оно такое?
в записи ^^loop.break крышечки относятся не к break, а к loop. Т.е. имеется в виду (^^loop).break, а не ^^(loop.break).
Не очень понимаю, зачем оператору break нужен loop. И так же ясно, что выйти можно только из цикла. В одних местах Вы стремитесь к наибольшей краткости, а в этом месте — нет. Даже case с двоеточием убираете из конструкции switch, оставляете одну константу. А тут такая расточительность.
такой код малополезен — проще создать переменную чуть раньше за скобкой.
Если чуть раньше, то значит
  • просто её объявить, без инициализации. Тогда придётся делать анализ: а не используется ли эта переменная в правой части присваивания, будучи неинициализированной?
  • Или инициализировать, но инициализация за скобкой будет бесполезной: значение в последующем будет изменено, а инициализирующее значение так и не понадобится.
Инициализация же в нужном месте с распространением видимости на уровень выше упрощает применение вывода типа через инициализацию.
А на практике может понадобиться создать «внешнюю» переменную в теле условного оператора. Вот только вопрос: что делать в случае, когда условие не выполняется — как должно отрабатывать обращение к этой переменной в последующем коде?
Инициализация переменной должна произойти во всех альтернативных ветвях кода во избежание использования неинициализированной переменной в правой части присваивания.

Но лучше это делать конструкцией вроде этой:
а = (если условие; "это правда" иначе "не верь этому")
не предполагает явное обозначение объявления новой переменной?
Да, не предполагает. Хотя, если требуется, можно уточнить тип инициализирующего значения.
В 11l обязательно указывать при инициализации, что объявляется новая переменная, с помощью ключевого слова
У Алексея Недори в его Тривиле сделано весьма любопытно
а = 9  // а — константа, ибо знак равенства
б := 0 // б — переменная, ибо присваивание
Да и зачем вообще объявлять новую переменную? Вы же стремитесь к краткости в языке. Ясное дело, что описание типов необходимо для аргументов функции или полей структур данных, без этого не обойтись. С глобальными переменными тоже надо думать, как быть.
в 5.4 и более новых версиях — писать break 0 вообще запретили.
И правильно сделали. Зачем нужны бессмысленные конструкции? Когда я написал про break 0, то это было «объяснение на пальцах». Я лишь уповал на логику, чтобы исключить путаницу. А не иллюстрировал будущие задумки.
в чём ошибка компиляции в записи var2*2? Это же простое умножение значения переменной var2 на число 2.
Я имел в виду, что выражение
 var4 = нечто
синтаксически правильно, в отличие от
 var2*2 = нечто
Точно так же
 break 4
не может быть заменено на
 break 2*2
Хотя в теории, ничто не мешает во время компиляции вычислить значение количества покидаемых циклов, если выражение вычислимо во время компиляции.
 break cos(0) + tg(π/2)
Но тогда придётся делать законным
 break 0
Но это всё игры разума, можно обойтись без пижонства, одним лишь обычным числом.

✅  2024/11/06 00:00, alextretyak          #28 

ИванАс
Наверное, можно заменить на product из itertools.
В данном случае да, можно.
Но восприятие кода при этом усложнится, несмотря на избавление от лесенки из if was_break.

Автор сайта
Кстати, что означает такое название языка?
Об этом можно догадаться из описания языка на его сайте — http://11l-lang.org/ru
Более подробно написано об этом на странице языка в Википедии (которую, правда, уже удалили, но есть сохранённая копия — https://11l-wikipedia.github.io/ru):
"""
В отличие от других языков программирования, ключевые слова 11l структурированы в иерархию. На верхнем уровне этой иерархии располагается 11 базовых/корневых ключевых слов. Данная черта легла в основу названия языка программирования 11l, где «l» означает «litterae» в латинском, «logos» в греческом (в значении «слово»), либо «letters» в английском (так как корневые ключевые слова языка можно сокращать до одной буквы).
"""
Не очень понимаю, зачем оператору break нужен loop.
Поэтому, исходя из названия языка, я не могу добавить в 11l ключевое слово break, т.к. в этом случае корневых ключевых слов станет больше, чем 11.
Даже case ... убираете из конструкции switch
С case аналогично — добавить это ключевое слово в 11l невозможно, не изменив название языка на 12l. 🤣
И так же ясно, что выйти можно только из цикла.
Не только. В 11l ещё есть switch.break. Впрочем, его полезность под вопросом. А вот switch.fallthrough может пригодиться.

К тому же, даже если «только из цикла», то остаётся вопрос: «а из какого именно цикла?» И вот тут этот "расточительный" синтаксис оказывается очень к месту: loop обозначает текущий цикл, ^loop обозначает вышестоящий цикл, loop(version) обозначает цикл с переменной цикла version и т.д. И к любому выбранному циклу можно применить любое ключевое "подслово" цикла, а именно break, continue, index, first_iteration и пр.
Или инициализировать, но инициализация за скобкой будет бесполезной
А зачем вообще необходимо использовать вложенные скобки в вашем примере? Можете привести более законченный/практичный пример где требуется запись ^переменная = ...? А то не очень понятно, что вы имели в виду.
Да и зачем вообще объявлять новую переменную?
Если честно, я даже не рассматривал этот вопрос всерьёз. Практически во всех компилируемых языках переменные необходимо объявлять явно, и я считал этот вопрос решённым и не требующим переосмысления. А сейчас вот даже и не знаю, что ответить, какие весомые аргументы привести в пользу явного объявления.

Прежде всего, мне нравится чётко видеть в коде, где объявляется/создаётся новая переменная, а где изменяется значение уже существующей.

И компилятор не пропустит случаи, когда в имени переменной была допущена ошибка:
fn calc_result()
var result = 0
if ...
rezult = 1 // ошибка компиляции, а не создание новой переменной с именем rezult
...
Также явное объявление позволяет создать новую переменную с уже использующимся именем во внутренней области видимости:
fn calc_result()
var r = 0
...
if ...
var r = 1 // без `var` будет использоваться внешняя `r`
print(r) // выводим значение внутренней `r`
...
return r // возвращаем значение `r`, объявленной в начале функции
Ясное дело, что описание типов необходимо для аргументов функции
Тип аргументов функции даже в компилируемом языке указывать необязательно. Так, в C++20 появилась возможность использовать auto вместо типа аргумента функции — в этом случае тип аргумента «определится» в месте вызова функции. А в 11l такое поведение получается в случае, когда тип аргумента вообще не указан (а только его имя).

Хотя для перегрузки функции с одинаковым количеством аргументов указать тип хотя бы некоторых из них, конечно, придётся явно.
Когда я написал про break 0, то это было «объяснение на пальцах». Я лишь уповал на логику, чтобы исключить путаницу.
Тогда соглашусь. Логика в этом есть.
Правда такую запись — break <число> — в других языках (кроме PHP) я не встречал.

✅  2024/11/09 01:01, Автор сайта          #29 

Данная черта легла в основу названия языка программирования 11l, где «l» означает «litterae» в латинском
Мне кажется, маркетологи убили бы Вас за такое название. 🤣
я не могу добавить в 11l ключевое слово break, т.к. в этом случае корневых ключевых слов станет больше, чем 11... добавить это ключевое слово в 11l невозможно, не изменив название языка на 12l
Вы сами придумали себе трудности, а потом героически их преодолеваете. Была когда-то такая забава — турнир поэтов. Поэты сперва придумывали какие-нибудь произвольные ограничения, например окончание строк на какие-то строго определённые слова. А потом укладывали свои стихи в такое прокрустово ложе. Вот и Вам, видимо, придётся строить модель парусника внутри стеклянной бутылки. Но Вы автор, Вы творец и Вам виднее.
Можете привести более законченный/практичный пример где требуется запись ^переменная = ...?
(^i = 0 цикл i < N; ++ i // i объявлена внутри скобок
. . .)
j = i // но видна за скобками
Практически во всех компилируемых языках переменные необходимо объявлять явно, и я считал этот вопрос решённым и не требующим переосмысления. А сейчас вот даже и не знаю, что ответить, какие весомые аргументы привести в пользу явного объявления.
Вывод типов, то есть инициализация как значения, так и типа, исключает такой класс ошибок, как использование неинициализированных переменных. Вывод типов создаёт иллюзию, что объявлять переменные не надо. Но это только иллюзия, которая придаёт сходство с языками с динамической типизацией.
мне нравится чётко видеть в коде, где объявляется/создаётся новая переменная, а где изменяется значение уже существующей.
Есть возможность почувствовать вкус иного стиля, попрактиковавшись на языках типа PHP или Basic. Тогда идею вывода типов легче принять. В языках с динамической типизацией тип переменной может меняться по ходу вычислений, а при выводе типов тип переменной задаётся один раз, при инициализации.
явное объявление позволяет создать новую переменную с уже использующимся именем во внутренней области видимости
Мне кажется, уникальность имён позволяет не париться на эту тему. А если приспичило, то можно неуникальные имена сопровождать именем области видимости. Но, думается, лучше не дублировать имена.
Хотя для перегрузки функции с одинаковым количеством аргументов указать тип хотя бы некоторых из них, конечно, придётся явно.
И тут склоняюсь к уникальности имён. Хотя полиморфизм времени компиляции даёт некоторую волю, не так уж и сильно ограничивает такая уникальность.
Правда такую запись — break <число> — в других языках (кроме PHP) я не встречал.
Я уже рассказывал историю своих взаимоотношений с break и continue. Когда-то придумал, что эти ключевые слова надо сделать с числовым параметром. Потом познакомился с PHP и увидел, что break в нём может иметь параметр, а вот continue нет. Потом написал и опубликовал эту идею в статье, которую мы сейчас комментируем. Потом появился язык Jancy, в котором и break, и continue имеют параметр. А автор языка Jancy просит меня включить свой язык в этот список.

✅  2024/11/13 15:57, Неслучайный читатель          #30 

Если не нравится break 2, можно заменить на break break
цикл
цикл
если условие
break break //выход из обоих циклов
То есть break N меняется на N штук break. Как в языках народов Полинезии: если существительное употребляется несколько раз, то столько штук и имелось в виду.

С continue N интереснее. Ведь чтобы перейти к продолжению цикла уровня N, надо сперва выйти из N-1 циклов. То есть continue N по факту означает break ... break continue: где break повторяется N-1 раз.

✅  2024/11/13 18:31, Борис К.          #31 

Ада
цикл1:loop
...
цикл2:loop
...
exit цикл1;
exit цикл2;
...
end loop цикл2;
...
end loop цикл1;

✅  2024/11/13 20:44, Неслучайный читатель          #32 

Длинновато с метками.

✅  2024/11/14 00:00, alextretyak          #33 

Автор сайта
Вы сами придумали себе трудности, а потом героически их преодолеваете.
А ведь и правда, можно посмотреть на это и с такой точки зрения. Как-то не думал об этом. Но всё-таки, синтаксис и возможности языка 11l определялись не исходя из его названия (к которому я пришёл далеко не сразу), а наоборот — когда я понял/пришёл к окончательному решению, что корневых ключевых слов достаточно одиннадцати, то решил оставить такое название с целью предотвратить ненужное раздувание языка.
Есть возможность почувствовать вкус иного стиля, попрактиковавшись на языках типа PHP или Basic.
Я уже давно напрактиковался в таких языках. Даже свой движок форума написал на PHP и JavaScript. И если PHP со своими вездесущими «долларами» перед именем каждой переменной и "аляповатой" и перегруженной стандартной библиотекой мне никогда не нравился, то JavaScript-ом я поначалу прямо таки восхищался. После C++ (причём версии C++03) с его излишней многословностью, JS ощущался как глоток свежего воздуха. Но восхищение со временем и опытом профессионального программирования (правда не на JS, а на Lua и C++) прошло. И пришло понимание, что строгая типизация всё-таки лучше (а строгая статическая — ещё лучше).

Но по опыту программирования на Python я, откровенно говоря, могу признать, что отсутствие необходимости явного объявления/создания переменных — штука весьма удобная. И вместе с тем, наверное самое большое, что меня в Python раздражает, это смежная/похожая его фича — единая область видимости для всех локальных переменных в функции, независимо от того, на каком уровне они создаются.

К примеру, догадываетесь, почему в этой строке кода (https://sourceforge.net/p/ pqmarkup/code/ci/ 0aafa73f124996c2be99483a4479cd79e1a43d02/ tree/pqmarkup.py#l624) написано for ii in ..., а не просто for i in ...? Да потому, что i уже используется в этой функции в качестве индекса текущего разбираемого символа входной строки. Но сначала-то я в этом месте написал просто i, в результате чего получилось, что выполнение данного кода приводило к «порче» переменной i, "объявленной" выше и использующейся в последующем коде. И я до сих пор иногда забываю про эту "особенность" Python и наступаю на эти грабли (т.е. забываю про то, что добавление невинного цикла for i глубоко внутри функции портит значение i, использующейся в окружающем коде).
Тогда идею вывода типов легче принять.
Вывод типов в 11l есть. Также как и в новых версиях практически всех популярных компилируемых языков программирования. Вывод типа переменной при её инициализации — это фактически уже стандарт, им никого не удивишь (и многие им даже злоупотребляют — у нас на работе был программист, который настолько любил писать всюду auto, появившийся в C++11 в новом качестве, что вместо int i = 0; он писал auto i = 0;). Но обсуждаем то мы другое, а именно «необходимость явного обозначения для объявления/создания новой переменной».
В языках с динамической типизацией тип переменной может меняться по ходу вычислений, а при выводе типов тип переменной задаётся один раз, при инициализации.
Дело не в «динамичности» типизации. В статически типизированном Rust тип переменной тоже может меняться. Эта фича называется “rebinding”. Фактически, Rust позволяет переобъявлять переменную с другим типом, скрывая переменную с таким же именем, объявленную ранее. Вот здесь есть пример кода: https://users.rust-lang.org/t/ rebinding-variables-with-let/12959 Но аналогичного эффекта можно добиться и в C++ использованием фигурных скобок:
{auto x = 1;
...
}{auto x = "Hello World!";
...
}
Единственное, что дополнительно позволяет делать Rust (и что не получится повторить в C++) — это использовать при инициализации новой переменной значение старой/скрываемой переменной с таким же именем:
let s = "123";
...
let s = s.len(); // `s` здесь "меняет" тип на число
// (теперь в `s` содержится длина строки `s`)
Заметьте, что раз ваш язык не предполагает явное обозначение объявления новой переменной, то в вашем языке такой трюк как в Rust повторить не получится.
(^i = 0 цикл i < N; ++ i // i объявлена внутри скобок
. . .)
j = i // но видна за скобками
Так ведь тут можно, как я уже писал выше «создать переменную чуть раньше за скобкой»:
i = 0
(цикл i < N; ++ i
. . .)
j = i
Или можно даже так:
i = 0; (цикл i < N; ++ i
. . .)
j = i
В последнем случае количество строк кода будет такое же. И даже количество символов такое же. И даже специальный синтаксис с префиксным «^» не нужен. И даже читаемость/понятность кода лучше.

✅  2024/11/18 09:47, veector          #34 

С точки зрения пользователя языков, выражу свое скромное мнение. Мне было бы удобно, что бы циклу можно было присвоить имя, если нужно, чтобы оперировать не с тем циклом, в контексте которого выполняются команды, а с тем циклом, с которым нужно и когда это нужно. Проиллюстрирую идею. Вместо меток:
while (true) 
{
switch (instr[i])
{
case '[':
nesting_level++;
break;

case ']':
if (--nesting_level == 0)
goto break_;
break;
};
i++;
};
break_:
Было бы удобно, например так:
while.levels (true) 
{
switch (instr[i])
{
case '[':
nesting_level++;
break;

case ']':
if (--nesting_level == 0)
break(while.levels); // Выход из блока кода конкретного while.
break; // Выход из блока кода текущего контекста case.
};
i++;
};
Чуть выше есть пример на языке Ада, где идея выхода из цикла на мой взгляд более корректная, чем метки. Метки и goto, явно пришедшие из ассемблера, очень не удобны для сопровождения активно изменяющегося прикладного кода. В системном же коде, без меток и goto обойтись нельзя.

✅  2024/11/18 12:05, Автор сайта          #35 

alextretyak
Вы сами придумали себе трудности, а потом героически их преодолеваете.
... можно посмотреть на это и с такой точки зрения. Как-то не думал об этом. ... синтаксис и возможности языка 11l определялись не исходя из его названия..., а наоборот — когда я понял/пришёл к окончательному решению, что корневых ключевых слов достаточно одиннадцати, то решил оставить такое название с целью предотвратить ненужное раздувание языка.
Но этим самым Вы сжигаете за собой мосты — после этого Вы не сможете добавлять в язык новые, возможности которые могут потребовать новые ключевые слова. Либо надо признать, что Ваш язык настолько совершенен, что в улучшениях не нуждается. Но тогда надо признать, что Вы большой оптимист. 🤣 Или дальше не собираетесь над ним работать. Типа «я покорил этот Эверест, а дальше мне неинтересно».
отсутствие необходимости явного объявления/создания переменных — штука весьма удобная.
Ну вот, мне даже убеждать Вас ни в чём не надо.
самое большое, что меня в Python раздражает, это ... — единая область видимости для всех локальных переменных в функции, независимо от того, на каком уровне они создаются. ... написал просто i, в результате чего получилось, что выполнение данного кода приводило к «порче» переменной i, "объявленной" выше
Но, может, это и хорошо? Уникальность имён внутри функций предотвратит путаницу с одноимёнными переменными. Да и при точном следовании рекомендациям по стилю программирования следует писать короткие, лаконичные функции. А в них и переменных мало. Тогда ситуации, когда «Боливар не снесёт двоих», будет легко избежать.
let s = s.len(); // `s` здесь "меняет" тип на число
[если] ваш язык не предполагает явное обозначение объявления новой переменной, то ... такой трюк ... повторить не получится.
А надо ли его повторять? Мне вспоминается старая байка, как программисты спорили, какой язык лучше — Алгол или Фортран. Один из них говорит: «Зато в этом языке есть многомерные массивы!» Второй отвечает: «Покажи мне хоть одну свою программу, где бы были многомерные массивы». И крыть было не чем.

Создать одноимённую переменную с новым типом можно было бы примерно так:
имя = скрыть старую переменную и создать новую (значение)
где имя слева — новая ячейка в памяти нового типа, который определяется исходя из параметра значение. То есть здесь имеет место быть вывод типа. При этом функция «скрыть старую переменную и создать новую» — полиморфна, и это полиморфизм времени компиляции. В принципе в этом можно увидеть тот же «let», только вид сбоку, но формально объявления не было.
i = 0; (цикл i < N; ++ i
. . .)
j = i
Согласен, что тут от выноса инициализации i за скобки мы ничего не теряем, и это обычно будет справедливо и в других случаях. А над обратными примерами подумаю и напишу позже.

veector
Было бы удобно
Да, « удобно» — это очень индивидуально. Это как размер одежды: кому-то удобно носить 50 размер одежды, а кому-то нет. Мне было бы удобно вот так:
(цикл истина; ++i
когда instr[i]
= '['
++ nesting_level
= ']'
-- nesting_level
если nesting_level = 0
выйти)
Но всё это индивидуально. Владислав Джавадов вообще предлагает идею меняемого синтаксиса: главным является промежуточное представление, а отекстовка (преобразование из этого представления в обычный текст) зависит от выбранного вида синтаксиса.
Чуть выше есть пример на языке Ада, где идея выхода из цикла на мой взгляд более корректная, чем метки.
Там как-то циклы как раз имеют метку:
цикл1:loop
...
цикл2:loop
...
exit цикл1;
exit цикл2;
...
end loop цикл2;
...
end loop цикл1;
метки «цикл1» и «цикл2» упомянуты аж по 3 раза. На мой взгляд это визуальный мусор.
В системном же коде, без меток и goto обойтись нельзя.
Если системный код пишется на ассемблере, то конечно. Если на других языках, то среди них найдутся такие, что отсутствие goto и меток не принесёт неудобств.

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

✅  2024/11/18 19:44, Борис К.          #36 

Именованные конструкции хороши при большой вложенности
цикл1:loop
...
блок3:if ... then
...
loop
...
exit цикл1;
exit блок3 when ...;
exit;
...
end loop;
....
end if блок3;
...
end loop цикл1;
Числовой break как-то неочевиден
(int i=0 loop i < $Imax; ++i
// ...
(while ...
// ...
(switch ...
case ...
// ...
(int j=0 loop j < $Jmax; ++j
// ...
(if условие exit 2)
)
)
)
)
IMHO, break и continue должны быть только классические, для текущего цикла. По тем же причинам, почему не нужен goto.

✅  2024/11/19 15:52, veector          #37 

Под "удобно" я имел в виду не "нравится — не нравится", не рюшечки, а удобство сопровождения активно изменяеющегося кода с целью минимизации ошибок в ходе изменения.

"в Си уже ничего не изменить" — это огромное заблуждение. Вы просто привыкли к Майкрософтовскому Си и GNU gcc Си, которые имеют множество расширений, которые ещё и не полностью совместимы между собой. А я из мира embedded и кроме этих двух есть ещё другие компиляторы со своими расширениями, не похожими ни на что другое. Естественно, классический "Hello world" или "мигание светодиодом" ими всеми компилируются без проблем. Но когда начинаются действительно серьезные вещи, системные, тогда без расширений ни одна задача не решается.

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

break в Си с числовым аргументом опасен когда идет изменение кода, например, когда меняют набор операторов if() на switch() и case или наоборот, забывая увеличивать или уменьшать уровень контекста из которого нужно выходить — вот что создает неудобства.

PS. Про синтаксисы я вообще молчал, но, раз заговорили, то лично я скромно считаю синтаксис Си идеальным, мое мнение окончательно и изменению не подлежит.

✅  2024/11/19 17:09, Gudleifr          #38 

скромно считаю синтаксис Си идеальным
K&R или ANSI ?

✅  2024/11/19 17:25, veector          #39 

K&R или ANSI ?
Очевидно же, мой, как в примере.

✅  2024/11/21 11:02, Автор сайта          #40 

Под "удобно" я имел в виду не "нравится — не нравится"
Удобство, лаконичность, ясность — это всё субъективно, потому что эти оценки даёт человек. А вот краткость можно измерить количеством символов. Надёжность — числом ошибок на 1000 строк кода. Продуктивность — временем реализации задачи. Вот это объективно, как правило.
"в Си уже ничего не изменить" — это огромное заблуждение. ... когда начинаются действительно серьезные вещи, системные, тогда без расширений ни одна задача не решается.
Неужели в вашем расширенном Си есть новые ключевые слова? Или запретили void*? Или вдруг функции начали явно делиться на чистые и нечистые? Вряд ли. Скорее всего, используются какие-то специфичные библиотеки.

Получить же несовместимость, когда одними компиляторами программа принимается, а другими нет, — легко. Но это изъяны языка, стандарты которого предусматривают неопределённое поведение, на усмотрение разработчика компилятора.
break в Си с числовым аргументом опасен когда идет изменение кода, например, когда меняют набор операторов if() на switch() и case или наоборот, забывая увеличивать или уменьшать уровень контекста из которого нужно выходить — вот что создает неудобства.
По-моему, отступы и программирование ступеньками дают отчётливую информацию об уровне.
считаю синтаксис Си идеальным
Если б я так считал, то этого сайта не было.

✅  2024/11/21 15:33, veector          #41 

Неужели в вашем расширенном Си есть новые ключевые слова?
Я не предлагал делать расширение для языка Си, а просто иллюстрировал пример, как это могло бы быть. Вот, например, OpenMP — вот это расширение. А в embedded компиляторах, так, "костылики":
__no_init int __regvar x @ __R4;
По-моему, отступы и программирование ступеньками дают отчётливую информацию об уровне.
Однако это не защищает от ошибок в числовой константе break при изменении уровней. Потом вы это найдете, но это будет потом, и возможно когда у заказчиков что-то не работает.

✅  2024/11/21 23:28, Автор сайта          #42 

OpenMP, __no_init, __regvar, @ __R4
Да, в Си можно натянуть сову на любой глобус. 🤣
это не защищает от ошибок в числовой константе break при изменении уровней.
Неужели не защищает? Ведь всё наглядно. Да и константу предлагали заменить несколькими ключевыми словами. В принципе, неплохое предложение.

✅  2024/11/22 00:00, alextretyak          #43 

break; // Выход из блока кода текущего контекста case.
Вот уж не думал, что найдётся человек, который будет защищать break в case-ах switch-а в Си. Это же явно неудачное решение с точки зрения дизайна синтаксиса языка программирования. И его неудачность признают разработчики большинства современных ЯП, где никакого break в switch-ах (или его аналогах) нет.

Даже в C#, несмотря на его максимальную близость к Си (ну, после C++ и Objective-C), неявное "проваливание" в case-ах запретили (кроме пустых case). "Проваливание" в C# (а также в D) необходимо обозначать в коде явно с помощью goto case. В языках Go и Swift для "проваливания" используется специальное ключевое слово fallthrough.

Собственно, только для пустых case оно, это неявное "проваливание" и используется в реальном коде. Но им есть гораздо более удачная альтернатива. Так, вместо
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
<...>
}
в Pascal-е можно писать так:
case ch of
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9': <...>
end;
или даже так:
case ch of
'0'..'9': <...>
end;
А разработчики языков, скопировавших по глупости поведение Си, осознали эту ошибку, и рекомендуют всегда вставлять комментарий, который говорит «да, здесь не забыли поставить break, а так и задумано».

Вот цитата из “Java Code Conventions” (https://www.oracle.com/technetwork/java/codeconventions-150003.pdf), написанного в 1997(!) году:
Every time a case falls through (doesn't include a break statement), add a comment where the break statement would normally be.

✅  2024/11/22 13:04, veector          #44 

Да, в Си можно натянуть сову на любой глобус.
Это везде так. Когда задача не решается, предусмотренным языком образом, то её решает компилятор, допуская свои расширения. Так устроен мир.
Неужели не защищает? Ведь всё наглядно. Да и константу предлагали заменить несколькими ключевыми словами. В принципе, неплохое предложение.
Каким образом может защитить, если цифру или количество ключевых слов вы ставите самостоятельно? В начале статьи есть пример на JAVA с именованными блоками, их же не просто так придумали. Жаль, что не придумали сразу в Си.
Вот уж не думал, что найдётся человек, который будет защищать break в case-ах switch-а в Си.
А я и не защищаю, а наоборот, написал в чем проблема с точки зрения сопровождения изменяющегося кода.

✅  2024/11/23 00:00, alextretyak          #45 

А я и не защищаю
Как это так, ведь вы же сами написали: «лично я скромно считаю синтаксис Си идеальным». И раз вы не уточняли («считаю синтаксис Си (кроме break в switch) идеальным»), то по умолчанию подразумевается, что вы считаете весь синтаксис Си идеальным. Или что вы хотели тогда сказать, что «синтаксис Си — идеальный, но... в нём есть изъяны, вроде break в switch». Так получается?

Отношение к break в switch в языке программирования может быть только одно из двух — либо "за", либо "против". Тут на двух стульях усидеть никак не получится. Так вы "за"? Или "против"? Если "за", то см. моё предыдущее сообщение, где я убедительно показал, что такое решение признано плохим многими разработчиками ЯП. А если вы "против", то возникает противоречие с вашим же утверждением про идеальность синтаксиса Си.
наоборот, написал в чем проблема с точки зрения сопровождения изменяющегося кода.
Если бы в Си не было break в switch, тогда вместо break(while.levels) достаточно было написать просто break, и проблемы «с точки зрения сопровождения изменяющегося кода» в данном случае не было бы.
Так как (цитирую):
когда меняют набор операторов if() на switch() и case
никакое поведение уже имеющихся break никак бы не изменилось, т.к. break-и использовались бы только для выхода из циклов.

✅  2024/11/23 12:56, Клихальт          #46 

А может вместо потворствования юношескому максимализму к двум Вашим вариантам ответа "только за" и "только против" стоит добавить третий: "понять для чего именно это было сделано" и применять оное сообразно? 😉 Ведь конструкция switch, не требующая break, менее гибка и не позволяет совмещать код из разных вариантов выбора без раздувания кода. Кроме того, внутри циклов вместо break возможно использование continue, что позволяет оптимизировать переходы. Понятно, что всё это может за вас сделать и оптимизатор компилятора, но мне, например, по кайфу не полагаться на того парня, а делать самому то, что считаю сделать нужным.

✅  2024/11/23 13:06, Gudleifr          #47 

А может вместо потворствования юношескому максимализму к двум Вашим вариантам ответа "только за" и "только против" стоит добавить третий: "понять для чего именно это было сделано" и применять оное сообразно?
На это ответили ранее:
считаю синтаксис Си идеальным
Если б я так считал, то этого сайта не было.

✅  2024/11/23 15:02, Клихальт          #48 

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

✅  2024/11/23 15:16, Gudleifr          #49 

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

✅  2024/11/23 16:56, Клихальт          #50 

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

✅  2024/11/23 19:11, Gudleifr          #51 

В чём именно по вашему состоит вредность?
В потере инварианта цикла.
Вложенные циклы это отображение окружающей нас действительности на виртуальный мир программы
Совсем нет.
Я знаю четыре метода программирования http://www.compiler.su/pochemu-obrechyon-yazyk-fort.php#169
1. В методе "копипастим" вложенные циклы полезны, но т.к. этот метод сводится к максимизации использования того, что есть, то там все полезно. Там в одном скрипте могут встретится совершенно разнородные циклы по файлам, строкам, параметрам и т.д.
2. В структурном методе мы помним, что программа есть конечный автомат и цикл нужен для замыкания переборщика состояний. Усложнять состояния введением внутренних переборов — себе дороже.
3-4. В методах масштабирования и проблемно-ориентированного языка всякий цикл по уму должен быть инкапсулирован в объект, итератор или другую языковую конструкцию. Объекты, содержащий вложенный циклы естественно разделяются на несколько.

✅  2024/11/23 21:54, Борис К.          #52 

Gudleifr
2. В структурном методе мы помним, что программа есть конечный автомат и цикл нужен для замыкания переборщика состояний.
Рука-лицо

✅  2024/11/24 10:34, Клихальт          #53 

В потере инварианта цикла.
При должном навыке его можно потерять и в не вложенном цикле — сие не недостаток языка, а достоинство кодера. ;)

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

✅  2024/11/24 15:27, Gudleifr          #54 

Борис К., Клихальт
Извините, но я не вижу в ваших постах какого-то отсыла к тому, что написал я.

✅  2024/11/25 12:58, veector          #55 

@alextretyak
Синтаксис всего языка Си — лично я считаю идеальным. И в то же время, лично я считаю, что логику с break и continue можно было сделать лучше, причем в рамках текущего синтаксиса язык Си.

Данный сайт посвящен разрабатываемому языку и тут ребята хотят повторить тот же логический изъян (не синтаксический изъян), что и в Си с break и continue. Поэтому, решил высказать, чем же ещё плох логический подход с числовой константой для break, кроме того, что рассмотрено в исходной статье.
Если бы в Си не было break в switch, тогда вместо break(while.levels) достаточно было написать просто break ...
Всё-таки недостаточно, потому что, например, если есть два и более вложенных циклов, иногда надо выйти из всех циклов, разом. И мне, как пользователю языка и программисту, было бы удобнее указывать из какого именно цикла мне надо выйти, не высчитывая уровень вложенности, который может меняться в ходе сопровождения программы при сокращении или увеличении уровней вложенности. Да, это редкие события, поэтому, очень редко доставляют проблем, но, всё-таки доставляют. Причем так, что достаточно одного раза, чтобы запомнить на всю жизнь.

@Клихальт
Вот, я тоже разделяю вот эту вашу точку зрения "конструкция switch, не требующая break, менее гибка". Хотя, там, где можно обойтись без такой возможности языка, предпочитаю не делать case без break, но, иногда, да, приходится. Так-то язык Си в switch позволяет делать даже такое, от чего даже мне становится как-то не по себе: https://en.wikipedia.org/wiki/Duff%27s_device Кстати, Клихальт, какой язык программирования вам нравится?

@Гуд
Перечисленные вами методы программирования — это, практически, самоограничения, для само связывания по рукам и ногам. По жизни, встречается всё и даже в комбинации. И вложенные циклы, все-таки нужны, а как ими распорядиться, лучше доверить программистам, желательно, профессиональным.

✅  2024/11/25 13:05, Gudleifr          #56 

Перечисленные вами методы программирования — это, практически, самоограничения, для само связывания по рукам и ногам. По жизни, встречается всё и даже в комбинации
Именно так. Вы можете предложить пятый? Шестой? А "самосвязывание" получается автоматически, в силу органических недостатков методов. Особенно, при попытке их скомбинировать.

✅  2024/11/27 00:00, alextretyak          #57 

Клихальт
А может вместо потворствования юношескому максимализму ...
Бывают случаи, когда максимализм уместен. И данный случай — один из таких. К тому же, позиция «синтаксис языка Х — идеален» — это ещё больший максимализм. Тем более, когда Х это Си.
Ведь конструкция switch, не требующая break, менее гибка и не позволяет совмещать код из разных вариантов выбора без раздувания кода.
Нет. Выразительные возможности в обоих случаях абсолютно одинаковы. Вопрос только в принятых умолчаниях. В случае Си отсутствие break обозначает "проваливание", но практика программирования показала, что это неудачное решение, т.к. "проваливание" требуется гораздо реже, чем break. Поэтому в современных ЯП break в switch убрали, т.к. поведение "break" осуществляется по умолчанию. Но поведение "проваливания" по-прежнему доступно, только его необходимо обозначать в коде явно — чаще всего для этого используется ключевое слово fallthrough (которое я уже упоминал чуть раньше). Так вот, мне очевидно, что умолчание, принятое в Си — явно неудачно, и было бы любопытно увидеть хоть какой-то разумный аргумент в защиту этого умолчания.

А так как вариантов тут всего два — по умолчанию либо "break" (при этом ключевое слово break в switch становится ненужным), либо "проваливание" (при этом ненужным становится ключевое слово fallthrough) — оттого и получается такой максимализм в отношении к break в switch в языке программирования.

veector
Синтаксис всего языка Си — лично я считаю идеальным.
Ладно. С вами всё понятно. Для вас «идеальность синтаксиса языка» определяется персональными ощущениями при программировании на этом языке, а не стремящимся к объективности рациональным обоснованием.

✅  2024/11/27 00:16, Gudleifr          #58 

стремящимся к объективности рациональным обоснованием
И в чем оно заключается?

✅  2024/11/29 00:00, alextretyak          #59 

И в чем оно заключается?
В стремлении к объективности. :) К примеру, моё утверждение «"проваливание" требуется гораздо реже, чем break» вообще говоря субъективно, и основано только на моём личном опыте программирования. Чтобы сделать более объективное утверждение, можно взять какой-нибудь достаточно популярный проект с открытым исходным кодом и проанализировать его исходники для сбора статистики, показывающей насколько именно редко требуется "проваливание" в switch.

В качестве такого проекта можно взять ядро Linux. Я проанализировал исходный код версии 1.0, которая вышла аж в 1994 году, но язык Си с тех пор не сильно изменился, да и программисты писать код на Си лучше не стали. И объёма кода этой версии — 100 тыс. строк — вполне достаточно для ориентировочной оценки. (Для справки: последняя версия ядра 6.12 содержит в 300(!) раз больше кода — 1.5Гб (против 5Мб в версии 1.0) и свыше 30 млн. строк.)

Для анализа я использовал модуль pycparser на Python. Т.к. работает он довольно медленно, а код ядра Linux состоит из большого количества .c файлов и каждый .c файл включает довольно много заголовочных файлов, то я решил объединить их в один .c файл для ускорения разбора всего исходного кода. При этом, правда, пришлось исходники немного "обработать напильником", т.к. в .c файлах встречались объявления макросов с короткими именами (например, #define pos (vc_cons[currcons].vc_pos)), которые необходимо undefine-ить (с помощью #undef pos) в конце файла, а то иначе они ломают последующий код.

В результате получились такие цифры. Всего конструкций switch в коде ядра: 215 (чуть меньше, чем общее количество .c файлов, т.е. на один .c файл приходится примерно один switch). Конструкций switch, в которых есть не пустой и не последний case, который не оканчивается на break, на continue, на return или на goto: 26. Из них ложных срабатываний: 10. О причинах ложных срабатываний можно почитать в моём отчёте (https://github.com/alextretyak/break_in_switch/blob/master/report.txt), для составления которого я проанализировал каждый случай обнаруженного анализатором потенциального "проваливания" в switch — при запуске скрипт-анализатор выдаёт их в формате имя_исходного_файла:номер_строки:позиция_в_строке, всего таких случаев получилось 32 (хотя прокомментировать каждый случай я поленился — комментарии есть только у ложных срабатываний). Там же в репозитории мой скрипт-анализатор в файле break_in_switch.py и Release с архивом с "обработанным" кодом ядра.

Таким образом, итого получается, что в коде ядра Linux 1.0 "проваливание" используется только в 16 switch-ах из 215, т.е. в 7% switch-ей.

✅  2024/11/29 00:10, Gudleifr          #60 

В качестве такого проекта можно взять ядро Linux. Я проанализировал исходный код версии 1.0, которая вышла аж в 1994 году, но язык Си с тех пор не сильно изменился, да и программисты писать код на Си лучше не стали
Наоборот, к 94-му Си уже безвозвратно устарел.

Да и сама идея того, что один большой проект статистически заменит много мелких, не безупречна. Большая субъективность не означает объективности.

✅  2024/11/29 00:26, Автор сайта          #61 

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

Думаю, для проваливания вниз было бы уместно использовать ключевое слово then вместо fallthrough. Вот тут оно было бы к месту. В отличие от then в Паскале после if условие. Там оно так себе, «не пришей к звезде манжет».

✅  2024/11/29 08:57, Клихальт          #62 

Alextretyak, попробуйте проанализировать тексты из эпохи, когда ещё не принято было писать так, как написано ядро Linux, которое Вы проанализировали. Вот тексты первого компилятора Си, написанного на Си.
https://github.com/mortdeus/legacy-cc
Точную статистику я по ним не собирал, как то сделали Вы, но после того как пробежался по ним глазами, увидел, что там картина несколько отлична от той, что описали Вы.

❌  2024/11/29 11:29, Клихальт          #63   Комментарий скрыт

❌  2024/11/29 11:36, Вежливый Лис          #64   Комментарий скрыт

✅  2024/11/29 12:52, veector          #65 

@alextretyak, хорошо, вы сделали некоторую статистику:
Таким образом, итого получается, что в коде ядра Linux 1.0 "проваливание" используется только в 16 switch-ах из 215, т.е. в 7% switch-ей.
И что она для вас показывает и на какие выводы вас подталкивает? Что вы хотите этой статистикой показать?

✅  2024/11/30 00:00, alextretyak          #66 

Клихальт
https://github.com/mortdeus/legacy-cc ... там картина несколько отлична от той, что описали Вы.
Да, в этом коде статистика получается другая. Всего конструкций switch: 58 (в пересчёте на общее количество строк кода относительное количество switch'ей в этом коде в 5 раз выше, чем в коде ядра Linux 1.0). "Проваливание" используется в 11 switch'ах, т.е. в 19% switch'ей. Ложных срабатываний, в принципе, не было. Только пара спорных моментов. Например, в [этом месте](https://github.com/mortdeus/legacy-cc/blob/master/prestruct/c01.c#L86-L88) на мой взгляд было бы понятнее вместо d1++; d2++; с "проваливанием" написать d1+=2; d2+=2; break;.

veector
Что вы хотите этой статистикой показать?
  • Во-первых, то, что при использовании «break по умолчанию» вместо «"проваливание" по умолчанию» в подавляющем большинстве switch'ей (и, как следствие, в программе в целом) уменьшится количество кода (конечно, минимизация объёма кода — это не самоцель, но в данном случае речь идёт об избавлении от синтаксического мусора в виде вездесущих break в почти каждом case).
  • Во-вторых, синтаксис языка программирования должен по возможности препятствовать возникновению ошибок в коде. Если в каком-то case нету break в конце (либо return или continue) — то это скорее всего ошибка, т.к. "проваливание" требуется очень редко (согласно статистике). И язык Си своим «"проваливанием" по умолчанию» потворствует допущению таких ошибок в коде.
И есть ещё одно преимущество отсутствия break в switch никак не связанное со статистикой: конструкция switch может быть не statement'ом, а выражением и возвращать результат соответствующей ветки case. Почитайте про switch-выражения в Java: https://habr.com/ru/articles/443464/
В switch-выражениях break совсем не уместен. При этом switch-выражения вполне могут заменить традиционный switch-statement и в новых языках программирования поддерживать switch-statement смысла нет — достаточно только switch-выражений. В том же Kotlin'е switch-statement'ов нет, а есть только switch-выражения (правда ключевое слово используется другое — when).

✅  2024/11/30 00:07, Gudleifr          #67 

речь идёт об избавлении от синтаксического мусора
...
"проваливание" требуется очень редко (согласно статистике)
Ну, как бы, Ваша статистика это опровергает: 20% пользы — это много. Плюс "ложные срабатывания", т.е. полезный пофигизм.

✅  2024/11/30 00:46, Автор сайта          #68 

Вот тексты первого компилятора Си
А в чём смысл рассмотрения именно древних исходников?

Взял файл первый попавшийся файл last1120c/c00.c и посчитал, как построены switch. В 6 конструкциях switch встретил такие case:
  • проваливание после case — 7 раз,
  • заканчивается goto — 23 раза,
  • заканчивается exit — 2 раза,
  • заканчивается return — 26 раз,
  • удивительно, но ни одного break!
Не ахти какая статистика, но проваливание используется нечасто. О чём догадывался и раньше.
Что вы хотите этой статистикой показать?
Лично я увидел в статистике, что проваливание требуется редко. Поэтому логично сделать поведением по умолчанию выход из switch, а не проваливание. А если проваливание потребовалось, то на этот случай предусмотреть специальный оператор или ключевое слово.

❔  2024/11/30 12:56, Клихальт          #69 

Автор сайта,
А в чём смысл рассмотрения именно древних исходников?
Если быть честным до конца, то исходники ядра Linux 1.0 с точки зрения сегодняшнего дня это такие же древние исходники, хотя против их рассмотрения вы не имели ничего против. И они ровно также субъективны, как и любые другие исходные тексты на этом языке программирования, вне зависимости от времени их написания. Смысл же их приведения мною состоит в том, что это шаг в сторону из круга понятий и приёмов программирования, принятых одними и вбитых в головы другим. Это попытка показать, что может быть и иначе.
Не ахти какая статистика, но проваливание используется нечасто. О чём догадывался и раньше.
Давайте поставим вопрос несколько иначе: насколько часто для выхода из ветки оператора выбора используется именно break? И в данном случае ответ однозначный — в рассматриваем вами участке текста ни разу, а в целом крайне редко. Не спорю, в настоящее время ситуация совершенно иная и происходит это, по моему мнению, оттого что во времена, к коим относятся тексты, приведённого мною примера, основным языком разработки был язык ассемблера, где вся оптимизация кода лежала на плечах программиста, а не языки высокого уровня и оптимизирующие компиляторы, как сейчас, где чем дальше, тем больше программист всё больше отстраняется сначала от оптимизации, а затем уже и от самого кода, что с моей точки зрения в корне неверно.

alextretyak,
Во-первых, то, что при использовании «break по умолчанию» вместо «"проваливание" по умолчанию» в подавляющем большинстве switch'ей (и, как следствие, в программе в целом) уменьшится количество кода (конечно, минимизация объёма кода — это не самоцель, но в данном случае речь идёт об избавлении от синтаксического мусора в виде вездесущих break в почти каждом case).
Под уменьшением количества кода Вы верно имели ввиду уменьшение количества исходного текста программы, а не количества кода программы? Что ж до "избавлении от синтаксического мусора в виде вездесущих break в почти каждом case" позволю отослать Вас к приведённому мною примеру исходных текстов компилятора языка Си, где break вообще практически не используется для выхода из ветки оператора выбора. Что ж до "синтаксического мусора", то break, впрочем как continue и return, при помощи которых происходит выход из ветки выбора это не "синтаксический мусор", а конкретные операторы, осуществляющие конкретные действия, которые необходимы с точки зрения программиста в конкретном месте программы, а "синтаксическим мусором" как раз и является псевдооператор "проваливания", который нужен лишь для того, чтобы дать понять думающему за тебя компилятору, что твое действие осмысленно и ничего за тебя в этом месте додумывать не надо.

❔  2024/12/01 00:00, alextretyak          #70 

Предлагаю продолжить обсуждение тут: http://compiler.su/pereklyuchatel.php#16

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

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

Авторизация

Регистрация

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

Карта сайта


Содержание

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

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

●  Устарел ли текст как форма представления программы

●  Русский язык и программирование

●  Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

●  Некоторые «вкусности» Алгол-68

●  «Двухмерный» синтаксис Python

●  Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

●  Должна ли программа быть удобочитаемой?

●  Стиль языка программирования

●  Тексто-графическое представление программы

●●  Разделители

●●  Строки программы

●●  Слева направо или справа налево?

●  Комментарии

●●  Длинные комментарии

●●  Короткие комментарии

●●  Комментарии автоматической генерации документации

●●  Нерабочий код

●●  Помеченные комментарии

●  Нужны ли беззнаковые целые?

●  Шестнадцатиричные и двоичные константы

●  Условные операторы

●  Переключатель

●  Циклы

●●  Продолжение цикла и выход из него

●  Некошерный «goto»

●  Изменение приоритетов операций

●  Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?

●  Так ли нужны операции «&&», «||» и «^^»?

●  Постфиксные инкремент и декремент

●  Почему в PHP для конкатенации строк используется «.»?

●  Указатели и ссылки в C++

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

●  Оценка надёжности функции с несколькими реализациями

●  Использование памяти

●  Почему динамическое распределение памяти — это плохо

●  Как обеспечить возврат функциями объектов переменной длины?

●●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●●  Массивы переменной длины в C/C++

●●  Размещение объектов в стеке, традиционный подход

●●  Размещение объектов переменной длины с использованием множества стеков

●●  Размещение объектов переменной длины с использованием двух стеков

●●  Реализация двухстековой модели размещения данных

●●  Двухстековая модель: тесты на скорость

●●  Изменение длины объекта в стеке во время исполнения

●●  Размещение объектов переменной длины с использованием одного стека

●  Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

●  Безопасность и размещение объектов переменной длины в стеке

●  Массивы, структуры, типы, классы переменной длины

●  О хранении данных в стеке, вместо заключения

●  Реализация параметрического полиморфизма

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

Компилятор

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

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

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

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




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

2024/12/07 20:54 ••• Клихальт
Переключатель

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