43

有时当我在编程时,我发现某些特定的控制结构对我来说非常有用,但在我的编程语言中并不直接可用。我认为我最常见的愿望是“分裂”(我不知道实际上该怎么称呼它):

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

此代码的语义foo()是始终运行,然后检查条件。如果为真,则bar()运行,我们返回第一个块(因此foo()再次运行,等等)。感谢reddit 用户 zxqdms 的评论,我了解到 Donald E. Knuth 在他的论文“Structured programming with go tostatements”(参见第 279 页)中写到了这种结构。

您认为哪些替代控制结构是组织计算的有用方式?

我的目标是为自己和其他人提供有关结构化代码的新思维方式,以改进分块和推理。

注意:我不是在问如何概括所有可能的控制结构,无论是通过使用jneif/ goto、Lisp 宏、延续、单子、组合子、夸克还是其他任何东西。我在问什么专业化对描述代码有用。

4

28 回答 28

20

有时,我需要一个带有索引的 foreach 循环。可以这样写:

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

(我不是特别喜欢这种语法,但你明白了)

于 2010-11-28T03:14:19.980 回答
20

一个相当常见的是无限循环。我想这样写:

forever {
  // ...
}
于 2010-11-28T02:12:11.320 回答
19

大多数语言都有内置函数来覆盖常见情况,但是“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 += ", ";
}
于 2010-11-28T08:59:53.083 回答
18

与 else 循环:

while (condition) {
  // ...
}
else {
  // the else runs if the loop didn't run
}
于 2010-11-28T03:57:53.593 回答
13

如果你看一下 Haskell,虽然各种控制结构都有特殊的语法,但控制流通常是由类型捕获的。最常见的此类控制类型是 Monads、Arrows 和 applicative functors。因此,如果您想要一种特殊类型的控制流,它通常是某种高阶函数,您可以自己编写它,也可以在 Haskells 包数据库(Hackage)中找到一个,它非常大。

此类函数通常位于 Control 命名空间中,您可以在其中找到用于并行执行到错误处理的模块。通常在过程语言中发现的许多控制结构在 Control.Monad 中都有对应的函数,其中包括循环和 if 语句。if-else 是 haskell 中的关键字表达式,如果没有 else 在表达式中没有意义,但在 monad 中完全有意义,因此没有 else 的 if 语句被函数when和捕获unless

另一个常见的情况是在更一般的上下文中进行列表操作。函数式语言非常喜欢fold,Specialized 版本如mapfilter。如果你有一个单子,那么它就有一个自然的延伸fold。这被称为foldM,因此还有你能想到的任何专门版本的 fold 的扩展,比如mapMfilterM

于 2010-11-27T23:04:52.457 回答
13
{
    foo();
} split_while( condition ) {
    bar();
}

您可以使用常规非常轻松地完成此操作while

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

我现在经常这样做,因为我克服了对break.

于 2010-11-27T21:12:32.523 回答
11

这只是一个一般的想法和语法:

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

我不知道这是否有用或易于阅读,但这是一个示例。

于 2010-11-28T11:33:49.977 回答
10

使用(lisp 风格的)宏、尾调用和延续,所有这些都是古怪的。

使用宏,如果标准控制流结构不足以满足给定的应用程序,程序员可以编写自己的(以及更多)。它只需要一个简单的宏来实现您作为示例提供的构造。

使用尾调用,可以将复杂的控制流模式(例如实现状态机)分解为函数。

延续是一种强大的控制流原语(try/catch 是它们的受限版本)。结合尾调用和宏,复杂的控制流模式(回溯、解析等)变得直截了当。此外,它们在 Web 编程中很有用,因为使用它们可以反转控制反转;你可以有一个函数来询问用户一些输入,做一些处理,要求用户提供更多输入,等等。

套用 Scheme 标准,而不是在您的语言中堆积更多功能,您应该设法消除使其他功能显得必要的限制。

于 2010-11-27T21:38:46.027 回答
8
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

如果 3 个条件中的任何一个被评估为真,则 FIRST 和 THEN 块运行。FIRST 块在条件块之前运行,然后在条件块运行之后运行。

FIRST 和 THEN 语句之后的 ELSE 条件或最终写入独立于这些块。

它可以读作:

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

这些函数只是一种阅读形式。他们不会创造范围。它更像是 Basic 的 gosub/return。

作为讨论问题的有用性和可读性。

于 2010-11-28T12:25:18.463 回答
8

标记循环是我发现自己有时在主流语言中缺少的东西。例如,

int i, j;
for outer ( i = 0; i < M; ++i )
    for ( j = 0; j < N; ++j )
        if ( l1[ i ] == l2[ j ] )
           break outer;

是的,我通常可以用 a 来模拟它goto,但等效的 forcontinue会要求您将增量移动到标签之后的循环主体的末尾,这会损害可读性。您也可以通过在内部循环中设置一个标志并在外部循环的每次迭代中检查它来做到这一点,但它总是看起来很笨拙。

(奖励:我有时希望 a与andredo一起使用。它会返回到循环的开头而不评估增量。)continuebreak

于 2010-11-28T07:07:51.777 回答
8

如果不:

unless (condition) {
  // ...
}

虽然不是:

until (condition) {
  // ...
}
于 2010-11-28T03:31:09.690 回答
8

我建议使用“then”运算符。它在第一次迭代时返回左操作数,在所有其他迭代中返回右操作数:

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

在第一次迭代中,它将“”添加到所有其他迭代中添加“,”的结果中,因此您将获得一个包含以逗号分隔的每个项目的字符串。

于 2010-11-28T09:56:04.857 回答
6

怎么样

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

或者类似的东西。但是,我确实喜欢这样的想法,即可以根据需要使用调用的结果(如颜色示例中所示)。

于 2010-11-28T03:17:03.417 回答
5

我想我应该提到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 $}
于 2010-12-03T13:10:27.710 回答
5

D 的作用域守卫是一种不常见的有用控制结构。

于 2010-11-28T09:43:31.160 回答
4

另请注意,许多控制结构在 monadic 上下文中获得了新的含义,具体取决于特定的 monad - 查看 Haskell 中的 mapM、filterM、whileM、sequence 等。

于 2010-11-27T21:59:19.380 回答
4

取代的东西

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在循环后超出范围。

于 2010-12-03T00:29:17.127 回答
4

我想看到一个用于分组输出的关键字。而不是这个:

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

如果您有一个不错的平台、控件和/或报告格式,则不需要编写此代码。但令人惊讶的是,我发现自己经常这样做。最烦人的部分是最后一次迭代之后的页脚——在不重复代码的情况下,在现实生活中很难做到这一点。

于 2010-12-03T00:32:10.630 回答
4

ignoring- 忽略某个代码块中发生的异常。

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

使用ignoring控件构造,您可以更简洁、更易读地编写如下:

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

[ Scala 在其标准库的util.control包中提供了这个(以及许多其他异常处理控制结构)。]

于 2010-11-28T03:57:14.780 回答
3

这是一个笑话,但你可以像这样得到你想要的行为:

#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's 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;
}

ps 格式化这有点困难,我绝对不满意;然而,emacs 做得更糟。有人愿意尝试 vim 吗?

于 2010-11-28T00:47:25.370 回答
3

这可能不算数,但在 Python 中,我很不高兴没有做循环。

为了确保我没有得到这个答案的赞成票,我最终会对我在任何时间里使用的任何语言都没有 goto 感到恼火。

于 2010-11-28T06:58:58.503 回答
3
for int i := 0 [down]to UpperBound() [step 2]

在每种 C 派生语言中都缺少。

请在投票或发表评论之前考虑一下
这不是多余的for (int i = 0; i <= UpperBound(); i++),它具有不同的语义:

  1. UpperBound()只评估一次

  2. 案例UpperBound() == MAX_INT不会产生无限循环

于 2010-12-03T00:08:14.007 回答
3

如果您主要使用非函数式语言,那么 Python 中的生成器确实很新颖。更一般地说:延续、协同程序、惰性列表。

于 2010-11-28T04:28:48.197 回答
2
foo();

while(condition)
{
   bar();
   foo();
}
于 2010-11-28T03:06:49.880 回答
2

这类似于@Paul Keister的回复。

(咕哝,咕哝)几年前,我正在处理的应用程序有很多所谓的控制中断处理的变体——所有这些逻辑都将已排序的数据行分解为带有页眉和页脚的组和子组。由于应用程序是用 LISP 编写的,我们在一个名为 WITH-CONTROL-BREAKS 的宏中捕获了常见的习惯用法。如果我将该语法转换为流行的波浪形形式,它可能看起来像这样:

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 等的广泛普及,这种需求似乎不像以前那样出现了。但有时,我希望我手头有那个控制结构。

于 2010-12-12T00:58:22.653 回答
1

PL/I 风格的“for”循环范围怎么样?VB 等价物是:

' 计数 1, 2, ... 49, 50, 23, 999, 998, ..., 991, 990
  对于 I = 1 到 50、23、999 到 990 步长 -1

我能看到的最常见的用法是循环运行一个索引列表,然后再输入一个。顺便说一句, For-Each 的用法也很方便:

' Bar1, Bar2, Bar3 是一个 IEnum(Wazoo); 博兹是个Wazoo
  对于 Bar1、Bar2、Enumerable.One(Boz)、Bar3 中的每个 Foo 作为 Wazoo

这将对 Bar1 中的所有项目、Bar2、Boz 和 Bar3 中的所有项目运行循环。Linq 可能会毫不费力地允许这样做,但内在的语言支持可能会更有效一些。

于 2010-12-02T18:59:24.390 回答
1

许多语言中不可用的控制结构之一是 case-in 类型结构。类似于 switch 类型结构,它允许您拥有一个格式整齐的可能选项列表,但匹配第一个正确的选项(而不是第一个匹配输入的选项)。这样的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构造表示,并且它可以非常方便地以一种干净、易读的方式表达该逻辑。

于 2011-11-14T21:56:06.137 回答
0

用一个移动的窗口(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

于 2012-06-06T17:06:45.360 回答