Здравствуйте, Бойко!
> А вот полным решением проблемы с избыточностью имен в struct в Си я горжусь
> :) И в учебниках по программированию на Си, и в реальных программах — тонны
> этой избыточности, но решение показывает, что она не нужна.
Согласен, красиво и компактно. Но с другой стороны, я уже пять лет работаю в
коммерческом проекте на Си99, и по стилю кода у нас не принято использовать
typedef. Соответственно, всюду пишем struct Foo, struct Bar * и т.д. И, что
интересно, я привык к этой конструкции. Из кода видно, что типом переменной
может быть либо примитивный тип (uint32_t, скажем), структура, указатель на
что-то (в том числе и указатель на структуру), и как-то без struct уже
воспринимается неестественно.
> Это если принять, что в структурном программировании их нет.
> Но данная точка зрения не единственная.
Для меня структурное программирование — это использование конструкций с одним
входом и одним выходом. Классика: присваивание, вызов подпрограммы, следование
инструкций, if/else, while, do/whie, цикл со счётчиком (for Паскаля),
подпрограммы с одним return’ом в самом конце. Сюда можно добавить цикл foreach,
перебирающий элементы итератора. Основные свойства: для каждого оператора
программы можно синтаксически указать, какие операторы предшествовали, какие
операторы следуют. В начало цикла можно попасть либо после последнего оператора
перед циклом, либо после последнего оператора конца цикла. В точку после
if/else — либо после последнего оператора ветки then, либо ветки else. В
составном операторе — последовательности инструкций — можно быть уверенным, что
если управление передалось на её начало, оно обязательно дойдёт до конца. В
таких структурных программах циклы управляются исключительно своими заголовками
—не заглядывая в тело цикла, можно назвать условие завершения.
Но, понятно, это сильные ограничения на стиль программирования, и бывают
задачи, когда чистый структурный код читается и поддерживается хуже, чем
прагматичный неструктурный. Например, бывают случаи, когда даже goto
существенно упрощает и проясняет программу (но это не значит, что можно писать
по одному goto на каждые 10 строк кода, если конечно, речь идёт не о
ассемблере).
Я, лично, предпочитаю писать структурно, если задача не требует иного. И люблю
ту идиому с while для поиска.
> Признано, что придерживаться какого-нибудь множества структурных схем нужно,
> но по-моему так же нужно выбор этого множества время от времени
> пересматривать. Иначе в императивном программировании мы бы остались с
> Фортраном или Алголом-60, у каждого из которых имеется единственный оператор
> цикла. А в функциональном программировании так и не пришли бы к лексическому
> связыванию, частичному применению, одноаргументности и композиции функций и
> сопоставлению с образцом — остались бы с Лиспом, у которого их и сегодня нет,
> за исключением лексического связывания.
А они и пересматриваются. Например, в последние годы многие императивные языки
программирования обрели цикл foreach, перебирающий элементы некоторого
итератора, характерного для языка. Даже C++, в котором for(type var: cont) {…}
эквивалентен for(auto p = begin(cont); p != end(cont); ++p) {type var = *p; …}.
Кстати, цикл foreach иногда реализуют на Си при помощи хитрых макросов.
Например:
https://github.com/kroki/Cuckoo-hash/blob/master/src/cuckoo_hash.h#L159-L192
> …или Алголом-60, у каждого из которых имеется единственный оператор цикла
Удивился, поскольку помнилось, что в Алголе есть и for, и while. Открыл отчёт
по Алголу (revised report, 1962) — был очень витиеватый цикл for, который мог
быть даже while.
for j := 1 step 1 until 10 do — переберёт числа 1…10
for j := 1 step 1 until 10, 20 step 1 until 30 do — переберёт 1…10, 20…30
for j := 2, 3, 5, 7, 11, 13 do — переберёт несколько простых чисел
for j := 42 while x > 7 do — будет повторять тело цикла с параметром 42, пока x
> 7
for j := 42 while x > 7, 666 while x > 0, 100, 1 step 1 until 10 do — сначала
прокрутит некоторое количество раз 42, потом несколько раз 666, потом один раз
100, потом числа от 1 до 10.
В Алголе-68 тоже был один оператор цикла, который мог быть и for, и while, и
гибридом. Кстати, вопрос на засыпку: почему в Алголе-68 и Bash нет цикла с
постусловием (управляющие конструкции Bash слизаны с Алгола-68 почти дословно)?
> Впрочем, вред слишком узкого понимания структурности для императивного
> программирования был осознан и подробно обоснован еще в начале 1970-х.
Впрочем, и польза от разумной структурности тоже вполне обоснована. Сейчас уже
является обычной практикой программирование без активного использования goto,
ряд языков программирования не поддерживают его синтаксически (Java, Python). А
ведь когда-то goto в программах на высокоуровневых языках встречался столь же
часто, как и if’ы с циклами — я не говорю про древний Фортран, где иначе
программировать было нельзя. (Читал как-то отечественную книжку 80-х годов про
Симулу-67. Редкий метод в ней обходился без goto.)
-----Original Message-----
From: Boyko Bantchev [mailto:[email protected]]
Sent: Wednesday, August 9, 2017 1:58 PM
To: [email protected]
Subject: Re: FW: Рефал умер?
Здравствуйте, Александр!
> Понравилась реализация необязательного параметра n в макросе
Спасибо. Но этот прием не я придумал. Видел где-то.
А вот полным решением проблемы с избыточностью имен в struct в Си я горжусь :)
И в учебниках по программированию на Си, и в реальных программах — тонны этой
избыточности, но решение показывает, что она не нужна.
> Мой пример выглядел бы так:
>
> int row, col; // используются после цикла, нельзя объявлять внутри.
> loop (
> row = 0; col = 0
> , row < MAXROW && elem_at(row, col) == 0 , ++col;
> if (col == MAXCOL) {
> col = 0;
> ++row;
> }
> )
> // тело пустое, как я и предполагал
> fin();
Возможно, вы смотрели определение loop в статье?
То, что в файле m.cpp, является более поздним и с ним данный фрагмент
не сработает. Ваш вариант поиска я записал бы так:
loop (row = 0; col = 0 , row < MAXROW)
exitif (elem_at(row, col) != 0)
upd (if (++col == MAXCOL) { col = 0; ++row;}) fin();
Преимущества:
проверка нахождения нужного элемента выделена самостоятельно;
искомое условие для элемента сформулировано положительно;
тело цикла не пусто;
обновление индексов лучше сосредоточено в одной конструкции.
> Фишка этого примера в том, что он записывается целиком в рамках структурного
> программирования: без goto, break и return.
Это если принять, что в структурном программировании их нет.
Но данная точка зрения не единственная.
> Плата за структурное программирование. В варианте Сергея Абрамова (в
> следующем письме) вводится дополнительная булевская переменная и тоже
> требуется дополнительная проверка
И, кроме того, дополнительная переменная оказывается нелокальной для цикла,-ов,
хотя ей лучше быть локальной, ведь дальше она не используется.
Плата за структурное программирование? Скорее, по-моему, это плата за
определенное, очень конкретное понимание структурного программирования. Но
единого определения этого понятия, с которым все были бы согласны, нет. Зачем
подгонять решения задач под какое-то представление о структурности, которое
даже не является общепризнанным? Что получаем за «плату»? Ведь решения
становятся в той или иной мере искусственными (признаком чего является
избыточность имен и действий), а что получаем навстречу и так ли оно ценно? Не
лучше ли пересмотреть наше понимание о структурности?
Допустим, кто-то постановил, что программировать структурно — это только через
while и простую последовательность. Даже if не нужен, он выражается через
while. Но такое понимание структурности не берет во внимание ни локальность
связываний имен, ни количество тех же имен и вообще элементов программ, ни
обоснованность введения всех этих элементов с точки зрения решаемой задачи (а
не соблюдения заранее придуманных правил «структурности»).
Признано, что придерживаться какого-нибудь множества структурных схем нужно, но
по-моему так же нужно выбор этого множества время от времени пересматривать.
Иначе в императивном программировании мы бы остались с Фортраном или
Алголом-60, у каждого из которых имеется единственный оператор цикла. А в
функциональном программировании так и не пришли бы к лексическому связыванию,
частичному применению, одноаргументности и композиции функций и сопоставлению с
образцом — остались бы с Лиспом, у которого их и сегодня нет, за исключением
лексического связывания.
Впрочем, вред слишком узкого понимания структурности для императивного
программирования был осознан и подробно обоснован еще в начале 1970-х.