为什么seq计算生成器不允许“let!”

时间:2012-04-18 08:25:19

标签: f#

我注意到以下代码在您尝试编译时会出错:

let xx =
  seq {
    let! i = [ 1; 2 ]
    let! j = [ 3; 4 ]
    yield (i,j)
  }

这给出的错误是“错误FS0795:不再允许在序列表达式中使用'let!x = coll'。而是使用'for x in coll'。”此消息是当然清楚并演示如何解决它;固定代码是:

let xx =
  seq {
    for i in [ 1; 2 ] do
      for j in [ 3; 4 ] do
        yield (i,j)
  }

我的问题不是如何解决这个问题,而是为什么“让!”首先不允许在序列表达式中?我可以看到让我们这样的事实!迭代表达式可能会让一些人感到意外,但这不足以禁止构造。我也看到“for”在这里是如何更强大的,因为“let!”的版本在迭代范围内烘焙为“直到序列表达式结束”。

然而,能够迭代序列而不必缩进代码正是我所寻找的(用于遍历树结构)。我假设要获得这个语义,我将不得不创建一个新的表达式构建器,其行为主要类似于“seq”表达式构建器,但允许“let!”对于迭代,不是吗?


根据Brian的评论添加,为我的潜在问题提供解决方案:

我没有意识到 for 块中的缩进是不需要的,第二个样本可以重写为:

let xx =
  seq {
    for i in [ 1; 2 ] do
    for j in [ 3; 4 ] do
    yield (i,j)
  }

...在遍历树结构时摆脱了不断增加的缩进。语法甚至允许 for 语句之间的其他语句,而不需要额外的缩进,如:

let yy =
  seq {
    for i in [ 1; 2 ] do
    let i42 = i+42
    for j in [ 3; 4 ] do
    yield (i42,j)
  }

现在,如果我能弄清楚为什么我认为这些陈述需要缩进......

1 个答案:

答案 0 :(得分:8)

就在几个星期前,我和Don Syme一起撰写了一篇论文,试图解释F#计算表达式(如序列表达式,异步工作流程等)中语法选择背后的一些动机。你可以find it here。它没有给出你的问题的明确答案,但它可能有所帮助。

通常,如果您有某个类型M<'T>并且您正在定义计算构建器,则可以添加方法ForBind以启用for和{{1语法:

let!

For : seq<'T> -> ('T -> M<'T>) -> M<'T> Bind : M<'T> -> ('T -> M<'T>) -> M<'T> 的输入应始终为某个序列For,而seq<'T>的输入应为您要定义的Bind类型。

在序列表达式中,这两个操作具有相同的类型,因此它们必须执行相同的操作。尽管序列表达式可以同时提供两者,但是允许只有一个是一个好主意,因为对一件事使用两个不同的关键字会让人感到困惑。一般原则是M<'T>块中的代码应该像普通代码一样 - 并且由于comp { .. }存在于普通F#代码中,因此在for的计算表达式中使用相同的语法是有意义的。