Полезные альтернативные структуры управления? - PullRequest
43 голосов
/ 27 ноября 2010

Иногда, когда я программирую, я обнаруживаю, что какая-то конкретная структура управления была бы очень полезна для меня, но не доступна напрямую в моем языке программирования. Я думаю, что моим самым распространенным желанием является что-то вроде «раздельного времени» (я понятия не имею, как на самом деле это назвать):

{
    foo();
} split_while( condition ) {
    bar();
}

Семантика этого кода заключается в том, что foo() всегда выполняется, а затем проверяется условие. Если true, то bar() запускается и мы возвращаемся к первому блоку (таким образом, снова запускаем foo() и т. Д.). Благодаря комментарию пользователя reddit zxqdms я узнал, что Дональд Кнут пишет об этой структуре в своей статье «Структурированное программирование с go to операторами» (см. Страницу 279) .

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

Моя цель здесь - дать себе и другим новые взгляды на структурирование кода, чтобы улучшить разбивку на части и рассуждения.

Примечание : я не спрашиваю о том, как обобщить все возможные структуры управления, будь то с помощью jne, if / goto, макросов Lisp, продолжений , монады, комбинаторы, кварки или что-то еще. Я спрашиваю, какие специализации полезны при описании кода.

Ответы [ 28 ]

20 голосов
/ 28 ноября 2010

Иногда мне нужно иметь цикл foreach с индексом.Это можно написать так:

foreach (index i) (var item in list) {
  // ...
}

(мне не очень нравится этот синтаксис, но вы поняли идею)

20 голосов
/ 28 ноября 2010

Довольно распространенным является бесконечный цикл. Я хотел бы написать это так:

forever {
  // ...
}
18 голосов
/ 28 ноября 2010

Цикл с остальным:

while (condition) {
  // ...
}
else {
  // the else runs if the loop didn't run
}
18 голосов
/ 28 ноября 2010

Большинство языков имеют встроенные функции, чтобы покрыть общие случаи, но циклы "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 += ", ";
}
14 голосов
/ 28 ноября 2010
{
    foo();
} split_while( condition ) {
    bar();
}

Вы можете сделать это довольно легко, используя обычные while:

while (true) {
    foo();
    if (!condition) break;
    bar();
}

Я делаю это довольно часто, когда преодолел свое иррациональное отвращение к break.

13 голосов
/ 28 ноября 2010

Если вы посмотрите на Haskell, хотя существует специальный синтаксис для различных структур управления, поток управления часто захватывается типами.Наиболее распространенным видом таких типов управления являются монады, стрелки и аппликативные функторы.Поэтому, если вам нужен специальный тип потока управления, это обычно какая-то функция более высокого порядка, и вы можете написать ее самостоятельно или найти ее в базе данных пакетов Haskells (Hackage), которая довольно велика.

Такие функцииобычно находятся в пространстве имен Control, где вы можете найти модули для параллельного выполнения с обработкой ошибок.Многие управляющие структуры, обычно встречающиеся в процедурных языках, имеют функцию-аналог в Control.Monad, среди которых есть циклы и операторы if.If-else - это ключевое слово-выражение в haskell, если без else не имеет смысла в выражении, но имеет смысл в монаде, поэтому операторы if без else фиксируются функциями when и unless.

Другим распространенным случаем является выполнение операции со списком в более общем контексте.Функциональные языки довольно любят fold, а специализированные версии, такие как map и filter.Если у вас есть монада, то есть естественное расширение fold.Это называется foldM, и поэтому существуют также расширения любой специализированной версии сгиба, о которой вы можете подумать, например mapM и filterM.

11 голосов
/ 28 ноября 2010

Это всего лишь общая идея и синтаксис:

if (cond)
   //do something
else (cond)
   //do something
also (cond)
   //do something
else
   //do something
end

ТАКЖЕ условие всегда оценивается. ELSE работает как обычно.

Это работает и для случая. Вероятно, это хороший способ устранить оператор разрыва:

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

Я не знаю, полезно ли это или просто читать, но это пример.

9 голосов
/ 28 ноября 2010

С макросами (в стиле lisp), хвостовыми вызовами и продолжениями все это странно.

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

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

Продолжения являются мощным примитивом потока управления (try / catch - их ограниченная версия). В сочетании с хвостовыми вызовами и макросами сложные шаблоны потоков управления (обратный путь, анализ и т. Д.) Становятся прямыми. Кроме того, они полезны в веб-программировании, так как с их помощью вы можете инвертировать управление; у вас может быть функция, которая запрашивает у пользователя некоторый ввод, выполняет некоторую обработку, запрашивает у пользователя дополнительный ввод и т. д.

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

8 голосов
/ 28 ноября 2010

, если нет:

unless (condition) {
  // ...
}

пока нет:

until (condition) {
  // ...
}
8 голосов
/ 28 ноября 2010

Помеченные циклы - это то, чего мне иногда не хватает в основных языках. например.,

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. Он вернется к началу цикла без оценки приращения.)

...