Ответ 1
То, что довольно распространено, - это бесконечный цикл. Я бы хотел написать это следующим образом:
forever {
// ...
}
Иногда, когда я программирую, я нахожу, что какая-то определенная структура управления мне очень полезна, но я не имею прямого доступа на моем языке программирования. Я думаю, что мое наиболее распространенное желание - это нечто вроде "раскола" (я понятия не имею, что на самом деле назвать это):
{
foo();
} split_while( condition ) {
bar();
}
Семантика этого кода будет заключаться в том, что foo()
всегда запускается, а затем проверяется условие. Если true, тогда выполняется bar()
, и мы возвращаемся к первому блоку (тем самым снова запускаем foo()
и т.д.). Благодаря комментарий пользователя reddit zxqdms, я узнал, что Дональд Э. Кнут пишет об этой структуре в своей статье "Структурированное программирование с операторами go to
" (см. стр. 279).
Какие альтернативные структуры управления, по вашему мнению, являются полезным способом организации вычислений?
Моя цель здесь - дать себе и другим новые способы мышления о структурировании кода, чтобы улучшить фрагментацию и рассуждения.
Примечание. Я не спрашиваю, как обобщать все возможные структуры управления, используя макросы jne
, if
/goto
, Lisp, продолжения, монады, комбинаторы, кварки или что-то еще, Я спрашиваю, какие специализации полезны при описании кода.
То, что довольно распространено, - это бесконечный цикл. Я бы хотел написать это следующим образом:
forever {
// ...
}
Иногда мне нужен цикл foreach с индексом. Это можно было бы написать так:
foreach (index i) (var item in list) {
// ...
}
(Я не очень люблю этот синтаксис, но вы понимаете)
Петля с else:
while (condition) {
// ...
}
else {
// the else runs if the loop didn't run
}
В большинстве языков есть встроенные функции для покрытия общих случаев, но петли "fencepost" всегда являются сложными задачами: циклы, где вы хотите что-то делать на каждой итерации, а также делать что-то еще между итерациями. Например, объединение строк с разделителем:
string result = "";
for (int i = 0; i < items.Count; i++) {
result += items[i];
if (i < items.Count - 1) result += ", "; // This is gross.
// What if I can't access items by index?
// I have off-by-one errors *every* time I do this.
}
Я знаю, что складки могут охватывать этот случай, но иногда вам нужно что-то императивное. Было бы здорово, если бы вы могли:
string result = "";
foreach (var item in items) {
result += item;
} between {
result += ", ";
}
{
foo();
} split_while( condition ) {
bar();
}
Вы можете сделать это довольно легко, используя обычный while
:
while (true) {
foo();
if (!condition) break;
bar();
}
Я делаю это довольно часто сейчас, когда я преодолел свое иррациональное отвращение к break
.
Если вы посмотрите на Haskell, хотя есть специальный синтаксис для различных структур управления, поток управления часто захватывается типами. Наиболее распространенным видом таких типов управления являются Монады, Стрелки и аппликативные функторы. Поэтому, если вы хотите использовать специальный тип потока управления, это обычно какая-то функция высшего порядка, и вы можете написать ее самостоятельно или найти ее в базе данных пакетов Haskells (Hackage), которая довольно большая.
Такие функции обычно находятся в пространстве имен Control, где вы можете найти модули для параллельного выполнения для обработки ошибок. Многие из контрольных структур, обычно встречающихся в процедурных языках, имеют функцию-аналог в Control.Monad, среди которых - циклы и операторы if. If-else - это выражение с ключевыми словами в haskell, если без else не имеет смысла в выражении, но имеет прекрасный смысл в монаде, поэтому инструкции if без else захватываются функциями when
и unless
.
Другим распространенным случаем является операция списка в более общем контексте. Функциональные языки очень любят fold
, а специализированные версии - map
и filter
. Если у вас есть монада, тогда существует естественное расширение fold
. Это называется foldM
, и для этого существуют расширения любой специализированной версии складки, о которой вы можете думать, например mapM
и filterM
.
С макросами (lisp -style), хвостовыми вызовами и продолжениями все это причудливо.
С макросами, если стандартные конструкции потока управления недостаточны для данного приложения, программист может написать свои собственные (и многое другое). Для реализации конструктов, которые вы приводили в качестве примера, требуется простой макрос.
С помощью хвостовых вызовов можно разложить сложные функции потока управления (например, внедрение конечного автомата) в функции.
Продолжения - это мощный примитив потока управления (try/catch - ограниченная версия). В сочетании с хвостовыми вызовами и макросами сложные шаблоны управления потоком (откат, разбор и т.д.) Становятся прямолинейными. Кроме того, они полезны в веб-программировании, так как с ними вы можете инвертировать инверсию управления; вы можете иметь функцию, которая запрашивает у пользователя некоторый ввод, выполняет некоторую обработку, запрашивает у пользователя больше ввода и т.д.
Чтобы перефразировать стандарт схемы, вместо того, чтобы накладывать больше функций на ваш язык, вы должны попытаться устранить ограничения, которые делают другие функции необходимыми.
Это просто общая идея и синтаксис:
if (cond)
//do something
else (cond)
//do something
also (cond)
//do something
else
//do something
end
ТАКЖЕ условие всегда оценивается. ELSE работает как обычно.
Это работает и для случая. Вероятно, это хороший способ устранить оператор break:
case (exp)
also (const)
//do something
else (const)
//do something
also (const)
//do something
else
//do something
end
можно прочитать как:
switch (exp)
case (const)
//do something
case (const)
//do something
break
case (const)
//do something
default
//do something
end
Я не знаю, полезно ли это или просто читать, но это пример.
если нет:
unless (condition) {
// ...
}
пока нет:
until (condition) {
// ...
}
Маркированные контуры - это то, что я иногда теряю из основных языков. например.
int i, j;
for outer ( i = 0; i < M; ++i )
for ( j = 0; j < N; ++j )
if ( l1[ i ] == l2[ j ] )
break outer;
Да, я обычно могу имитировать это с помощью goto
, но эквивалент для continue
потребует, чтобы вы переместили приращение в конец тела цикла после метки, что ухудшило читаемость. Вы также можете сделать это, установив флаг во внутреннем цикле и проверив его на каждой итерации внешнего цикла, но он всегда выглядит неуклюжим.
(Бонус: Мне иногда хотелось иметь redo
, чтобы идти вместе с continue
и break
. Он вернется к началу цикла без оценки приращения.)
Я предлагаю оператор "then". Он возвращает левый операнд на первой итерации и правый операнд на всех других итерациях:
var result = "";
foreach (var item in items) {
result += "" then ", ";
result += item;
}
в первой итерации он добавляет "к результату во все остальные, которые он добавляет",", поэтому вы получаете строку, содержащую каждый элемент, разделенный запятыми.
if (cond)
//do something
else (cond)
//do something
else (cond)
//do something
first
//do something
then
//do something
else (cond)
//do something
else
//do something
end
FIRST и THEN блокируются, если какое-либо из трех условных выражений оценивается как true. FIRST выполняется перед условным блоком, а THEN запускается после запуска условного блока.
ELSE условная или окончательная запись, следующая за оператором FIRST и THEN, не зависят от этих блоков.
Он может читаться как:
if (cond)
first()
//do something
then()
else (cond)
first()
//do something
then()
else (cond)
first()
//do something
then()
else (cond)
//do something
else
//do something
end
function first()
//do something
return
function then()
//do something
return
Эти функции - это просто форма для чтения. Они не будут создавать рамки. Это больше похоже на gosub/return from Basic.
Полезность и читаемость как вопрос обсуждения.
Как насчет
alternate {
statement 1,
statement 2,
[statement 3,...]
}
для циклического использования доступных операторов на каждом последующем проходе.
Изменить: тривиальные примеры
table_row_color = alternate(RED, GREEN, BLUE);
player_color = alternate(color_list); // cycles through list items
alternate(
led_on(),
led_off()
);
Изменить 2. В третьем примере выше синтаксис может быть немного запутанным, поскольку он выглядит как функция. Фактически, на каждом проходе оценивается только один оператор, а не оба. Лучшим синтаксисом может быть что-то вроде
alternate {
led_on();
}
then {
led_off();
}
Или что-то в этом роде. Однако мне нравится идея, что результат, который когда-либо вызывается, может быть использован по желанию (как в примерах цвета).
D ограждения области видимости являются полезной структурой управления, которая не наблюдается очень часто.
Я думаю, что я должен упомянуть CityScript (язык сценариев CityDesk), который имеет некоторые действительно причудливые петли конструкции.
В файле справки:
{$ forEach n var in (condition) sort-order $}
... text which appears for each item ....
{$ between $}
.. text which appears between each two items ....
{$ odd $}
.. text which appears for every other item, including the first ....
{$ even $}
.. text which appears for every other item, starting with the second ....
{$ else $}
.. text which appears if there are no items matching condition ....
{$ before $}
..text which appears before the loop, only if there are items matching condition
{$ after $}
..text which appears after the loop, only of there are items matching condition
{$ next $}
Также обратите внимание, что многие структуры управления получают новое значение в монадическом контексте, в зависимости от конкретной монады - посмотрите на mapM, filterM, whileM, последовательность и т.д. в Haskell.
ignoring
- игнорировать исключения, происходящие в определенном блоке кода.
try {
foo()
} catch {
case ex: SomeException => /* ignore */
case ex: SomeOtherException => /* ignore */
}
С конструкцией управления ignoring
вы можете писать ее более сжато и читательнее:
ignoring(classOf[SomeException], classOf[SomeOtherException]) {
foo()
}
[Scala предоставляет эту (и многие другие конструкции управления обработкой исключений) в своей стандартной библиотеке в пакете util.control
. ]
Что-то, заменяющее
bool found = false;
for (int i = 0; i < N; i++) {
if (hasProperty(A[i])) {
found = true;
DoSomething(A[i]);
break;
}
}
if (!found) {
...
}
как
for (int i = 0; i < N; i++) {
if (hasProperty(A[i])) {
DoSomething(A[i]);
break;
}
} ifnotinterrupted {
...
}
Я всегда чувствую, что должен быть лучший способ, чем введение флага только для выполнения чего-то после последнего (регулярного) выполнения тела цикла. Можно проверить !(i < N)
, но i
выходит за рамки после цикла.
Я хотел бы увидеть ключевое слово для группировки вывода. Вместо этого:
int lastValue = 0;
foreach (var val in dataSource)
{
if (lastValue != val.CustomerID)
{
WriteFooter(lastValue);
WriteHeader(val);
lastValue = val.CustomerID;
}
WriteRow(val);
}
if (lastValue != 0)
{
WriteFooter(lastValue);
}
как насчет следующего:
foreach(var val in dataSource)
groupon(val.CustomerID)
{
startgroup
{
WriteHeader(val);
}
endgroup
{
WriteFooter(val)
}
}
each
{
WriteRow(val);
}
Если у вас есть достойная платформа, элементы управления и/или форматирование отчетов, вам не нужно будет писать этот код. Но это удивительно, как часто я нахожу себя в этом. Самая раздражающая часть - нижний колонтитул после последней итерации - это трудно сделать в реальном жизненном примере без дублирования кода.
Это немного шутка, но вы можете получить такое поведение, как вы:
#include <iostream>
#include <cstdlib>
int main (int argc, char *argv[])
{
int N = std::strtol(argv[1], 0, 10); // Danger!
int state = 0;
switch (state%2) // Similar to Duff device.
{
do {
case 1: std::cout << (2*state) << " B" << std::endl;
case 0: std::cout << (2*state+1) << " A" << std::endl; ++state;
} while (state <= N);
default: break;
}
return 0;
}
p.s. форматирование было немного сложным, и я определенно не доволен этим; однако emacs делает еще хуже. Кто-нибудь хочет попробовать vim?
Генераторы на Python действительно оригинальны, если вы в основном работаете с нефункциональными языками. В более общем плане: продолжения, совлокальные подпрограммы, ленивые списки.
Это, наверное, не считается, но в Python я был расстроен, не было цикла do.
Anto гарантирует, что я не получаю никаких отклонений от ответа на этот ответ, я раздражаюсь на любом языке, на котором я работаю, в течение какого-то периода времени, в котором отсутствует goto's.
for int i := 0 [down]to UpperBound() [step 2]
Отсутствует на каждом C-производном языке.
Пожалуйста, подумайте, прежде чем проголосовать или написать комментарий:
Это не избыточно для for (int i = 0; i <= UpperBound(); i++)
, оно имеет другую семантику:
UpperBound()
оценивается только один раз
Случай UpperBound() == MAX_INT
не создает бесконечный цикл
foo();
while(condition)
{
bar();
foo();
}
Это похоже на ответ @Paul Keister.
(mumble, mumble) лет назад приложение, над которым я работал, имело множество вариаций так называемой обработки прерывания - вся эта логика, которая вступает в разбиение отсортированных строк данных на группы и подгруппы с верхними и нижними колонтитулами, Поскольку приложение было написано в LISP, мы захватили общие идиомы в макросе, называемом WITH-CONTROL-BREAKS. Если бы я должен был перенести этот синтаксис во все более популярную форму squiggly, это может выглядеть примерно так:
withControlBreaks (x, y, z : readSortedRecords()) {
first (x) : { emitHeader(x); subcount = 0; }
first (x, y) : { emitSubheader(x, y); zTotal = 0; }
all (x, y, z) : { emitDetail(x, y, z); ztotal += z; }
last (x, y) : { emitSubfooter(x, y, zTotal); ++subCount; }
last (x) : { emitFooter(x, subcount); }
}
В эту современную эпоху, с распространенными SQL, XQuery, LINQ и т.д., эта потребность, похоже, не возникает так сильно, как раньше. Но время от времени я хочу, чтобы у меня была эта структура управления.
Как насчет PL/I стиля "для" диапазонов циклов? Эквивалент VB:
' Counts 1, 2, ... 49, 50, 23, 999, 998, ..., 991, 990 For I = 1 to 50, 23, 999 to 990 Step -1
Наиболее распространенное использование, которое я вижу, - это запустить цикл для списка индексов, а затем добавить еще один. BTW, для каждого использования также может быть удобно:
' Bar1, Bar2, Bar3 are an IEnum(Wazoo); Boz is a Wazoo For Each Foo as Wazoo in Bar1, Bar2, Enumerable.One(Boz), Bar3
Это запустит цикл для всех элементов в Bar1, всех элементов в Bar2, Boz и Bar3. Linq, вероятно, допустил бы это без особых трудностей, но внутренняя поддержка языка могла бы быть немного более эффективной.
Одной из структур управления, которая недоступна на многих языках, является структура типа ввода-вывода. Подобно структуре типа коммутатора, он позволяет вам иметь аккуратно отформатированный список возможных параметров, но соответствует первому, который является истинным (а не первым, который соответствует вводу). A LISP такого типа (который имеет его):
(cond
((evenp a) a) ;if a is even return a
((> a 7) (/ a 2)) ;else if a is bigger than 7 return a/2
((< a 5) (- a 1)) ;else if a is smaller than 5 return a-1
(t 17)) ;else return 17
Или, для тех, кто предпочитает более C-образный формат
cond
(a % 2 == 0):
a; break;
(a > 7):
a / 2; break;
(a < 5):
a - 1; break;
default:
17; break;
В основном это более точное представление конструкции if/elseif/elseif/else
, чем коммутатор, и оно может чрезвычайно эффективно выразить эту логику чистым, понятным способом.
Как насчет итерации с движущимся окном (из n элементов вместо 1) через список? Это, по-моему, тангенциально связано с @munificent ответ.
Что-то вроде
#python
#sum of adjacent elements
for x,y in pairs(list):
print x + y
def pairs(l):
i=0
while i < len(l)-1:
yield (l[i],l[i+1])
i+=1
Это полезно для определенных типов вещей. Не поймите меня неправильно, это легко реализовать как функцию, но я думаю, что многие люди пытаются вывести циклы for
и while
, когда есть более конкретные/описательные инструменты для задания.