Подтвердить что ты не робот

Полезные альтернативные структуры управления?

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

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

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

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

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

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

4b9b3361

Ответ 1

То, что довольно распространено, - это бесконечный цикл. Я бы хотел написать это следующим образом:

forever {
  // ...
}

Ответ 2

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

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

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

Ответ 3

Петля с else:

while (condition) {
  // ...
}
else {
  // the else runs if the loop didn't run
}

Ответ 4

В большинстве языков есть встроенные функции для покрытия общих случаев, но петли "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 += ", ";
}

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

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

Ответ 8

Это просто общая идея и синтаксис:

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

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

Ответ 9

если нет:

unless (condition) {
  // ...
}

пока нет:

until (condition) {
  // ...
}

Ответ 10

Маркированные контуры - это то, что я иногда теряю из основных языков. например.

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

Ответ 11

Я предлагаю оператор "then". Он возвращает левый операнд на первой итерации и правый операнд на всех других итерациях:

var result = "";
foreach (var item in items) {
    result += "" then ", ";
    result += item;
}

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

Ответ 12

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.

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

Ответ 13

Как насчет

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();
}

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

Ответ 15

Я думаю, что я должен упомянуть 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 $}

Ответ 16

Также обратите внимание, что многие структуры управления получают новое значение в монадическом контексте, в зависимости от конкретной монады - посмотрите на mapM, filterM, whileM, последовательность и т.д. в Haskell.

Ответ 17

ignoring - игнорировать исключения, происходящие в определенном блоке кода.

try {
  foo()
} catch {
  case ex: SomeException => /* ignore */
  case ex: SomeOtherException => /* ignore */
}

С конструкцией управления ignoring вы можете писать ее более сжато и читательнее:

ignoring(classOf[SomeException], classOf[SomeOtherException]) {
  foo()
}

[Scala предоставляет эту (и многие другие конструкции управления обработкой исключений) в своей стандартной библиотеке в пакете util.control. ]

Ответ 18

Что-то, заменяющее

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 выходит за рамки после цикла.

Ответ 19

Я хотел бы увидеть ключевое слово для группировки вывода. Вместо этого:

        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);
        }

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

Ответ 20

Это немного шутка, но вы можете получить такое поведение, как вы:

#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?

Ответ 21

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

Ответ 22

Это, наверное, не считается, но в Python я был расстроен, не было цикла do.

Anto гарантирует, что я не получаю никаких отклонений от ответа на этот ответ, я раздражаюсь на любом языке, на котором я работаю, в течение какого-то периода времени, в котором отсутствует goto's.

Ответ 23

for int i := 0 [down]to UpperBound() [step 2]

Отсутствует на каждом C-производном языке.

Пожалуйста, подумайте, прежде чем проголосовать или написать комментарий:
Это не избыточно для for (int i = 0; i <= UpperBound(); i++), оно имеет другую семантику:

  • UpperBound() оценивается только один раз

  • Случай UpperBound() == MAX_INT не создает бесконечный цикл

Ответ 24

foo();

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

Ответ 25

Это похоже на ответ @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 и т.д., эта потребность, похоже, не возникает так сильно, как раньше. Но время от времени я хочу, чтобы у меня была эта структура управления.

Ответ 26

Как насчет 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, вероятно, допустил бы это без особых трудностей, но внутренняя поддержка языка могла бы быть немного более эффективной.

Ответ 27

Одной из структур управления, которая недоступна на многих языках, является структура типа ввода-вывода. Подобно структуре типа коммутатора, он позволяет вам иметь аккуратно отформатированный список возможных параметров, но соответствует первому, который является истинным (а не первым, который соответствует вводу). 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, чем коммутатор, и оно может чрезвычайно эффективно выразить эту логику чистым, понятным способом.

Ответ 28

Как насчет итерации с движущимся окном (из 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, когда есть более конкретные/описательные инструменты для задания.