你如何在F#中实现goto?

6
我是一名有用的助手,可以为您翻译文本。
我最喜欢的编程语言都有一个“goto”命令。也就是说,您可以创建一个标签,然后稍后中断程序的流程以转到该标签。这个结构的更多实用应用之一是创建一个无限循环,像这样:
 start:
 goto start

很遗憾,如果我正确理解编译器错误的话,我无法在F#中使用相同的语法。既然它似乎不被本地支持,那么我该如何在F#中实现goto命令呢?
当然,F#是一种足够强大的语言,可以实现如此简单的功能。其他语言(例如Javascript)虽然没有本地支持goto,但仍可通过插件实现
此外,作为函数式编程范例中的一种语言,我认为F#应该能够支持更高级别的goto:您可以将goto传递给goto

11
顺便问一下:这是个笑话吗?我看了一下你的声望,你肯定知道这个! - Random Dev
2
我和你一起,跳进嵌套闭包的中间听起来很有趣! - Daniel
3
唉,goto甚至没有被保留以便将来使用。我担心目前没有计划实现这个非常需要的功能。 - Daniel
3
Lambda:终极的Goto http://repository.readscheme.org/ftp/papers/ai-lab-pubs/AIM-443.pdf - gradbot
我感觉这里有一个笑话... - AK_
5个回答

12
let goto f = f()

let test() =
  let label = (fun () ->
    //rad code
    )
  //tight code
  goto label

一个小缺点是必须将所有代码都包装在闭包中。我不知道——对于得到像goto这样方便的东西来说,似乎并不太糟糕。


1
我喜欢这个答案...但是你应该在最后添加“let rec test...”,因为我们想要在块内部调用goto。 - Random Dev
为什么goto需要调用自身? - Daniel
@Daniel,我很好奇。您如何使用您提出的模式将我给出的C语言goto示例转换为代码? - gradbot
@gradbot:你只需要在所有函数调用周围加上gotogoto do_read等。我的答案很弱。所有goto做的就是调用一个函数。但它的语法与OP想要的相似(我想)。 - Daniel

8

你可以使用互递函数在F#中获得GOTO的行为。尾调用优化允许此GOTO特性且不将任何内容推入堆栈。

int parse() 
{
    Token   tok;

reading:
    tok = gettoken();
    if (tok == END)
        return ACCEPT;
shifting:
    if (shift(tok))
        goto reading;
reducing:
    if (reduce(tok))
        goto shifting;
    return ERROR;
}

这里的 do_read、do_shift 和 re_reduce 是标签。

type Token = END | SOMETHINGELSE

type Status = ACCEPT | ERROR

let parse gettoken shift reduce () =
    let rec do_read() =
        match gettoken() with
        | END -> ACCEPT
        | _ as tok -> do_shift tok

    and do_shift tok =
        if shift tok then
            do_read()
        else
            do_reduce tok

    and do_reduce tok =
        if reduce tok then
            do_shift tok
        else
            ERROR

    do_read()

代码来源 http://sharp-gamedev.blogspot.com/2011/08/forgotten-control-flow-construct.html


正如我在我(开玩笑)的回答中指出的那样:标签和函数调用之间实际上没有太大的区别。最大的区别可能是“落空”... 我不知道你如何在 F# 中实现它。当然,你的代码可行,因为每个函数都以一个函数调用结束。很棒的答案。 - Daniel
@Daniel:Fall through只是在前一个函数的结尾处尾调用下一个函数。 - J D

8
很遗憾,如果我正确理解编译器错误的话,我不能在F#中使用同样的语法。因此,既然似乎没有本地支持,我该如何在F#中实现goto命令呢?
正如Daniel所说,一个标签及其后续指令块可以被翻译成一个函数及其主体。然后,每个goto都变成了一个函数调用。你必须将所有本地变量作为参数传递,因为单独的函数有单独的作用域,当必要时,你必须从一个指令块添加到下一个指令块的尾部调用。然而,尾调用是一个更一般的概念。
你的start循环示例变成了:
let rec start () =  // .start
  start()           // goto start

请注意,一个不错的编译器实际上会将这个等效的高级代码重新编译成汇编语言中指令块之间的跳转/分支。主要的区别在于,堆栈框架必须重新组织,因为您可以在完全不同的环境之间进行尾调用。
此外,我认为作为函数式编程范式中的一种语言,F#应该能够支持更高级的goto:您可以将goto传递给其他goto。
是的,确实如此。在其他语言中,您不能传递标签,但在F#中可以传递函数,既可以作为函数调用中的参数,也可以作为从函数返回的值。其他语言(如Fortran)提供了计算的goto作为一个折中方案。
请注意,异步编程是这种技术的重要实际应用。当您调用异步函数时,您告诉它在完成时要跳转到哪里。例如,当您调用异步下载网页的函数时,您向其传递一个函数,它将在数据可用时调用该函数(本质上,硬件中断接收到您的最后一批数据时会触发您的高级托管代码来操作新鲜的数据,这非常酷)。现代语言通过在编译过程中组合这些类似goto的技术和额外的代码生成提供了编写高级可重用异步代码的工具。在像C#这样的其他语言中,您处于劣势,因为您想将多个异步调用包装在一个try..catch中,但是您不能,因为它们实际上分布在许多不同的函数中。

5
在其他答案中没有提到的一种方法是创建自己的计算生成器。我撰写了两篇文章,为F# 中的一个计算生成器 imperative { .. }(阅读第一篇第二篇),实现了一些命令式特性。
虽然它们并没有实现goto,但它们实现了continuebreak。您可以添加对goto的支持,但只能跳回到已执行的标签。以下是使用continue的示例:
imperative { 
  for x in 1 .. 10 do 
    if (x % 3 = 0) then do! continue
    printfn "number = %d" x }

1

如果你查看源代码,你会发现goto.js是作为代码的文本预处理器实现的。它使用正则表达式来查找和替换标签和跳转到适当的Javascript结构。

你可以使用这种方法来扩展任何语言(包括F#)。在.NET中,你可能可以以类似的方式使用T4。然而,在文本级别上操作语言通常更像是一种hack而不是一个合适的扩展(goto.js的作者自己说“认真的。永远不要使用这个。”),这种元编程通常是通过钩入语言AST来完成的。

当然,F#是足够强大的语言,可以实现如此简单的功能。

F#实际上对元编程的支持非常差。具有良好元编程能力的语言包括任何Lisp、OCaml(通过campl4)、Haskell(通过Template Haskell)、NemerleScalaBooTraceur实现了适当的JavaScript AST元编程能力。但据我所知,F#还没有类似于这样的东西。

应该能够支持更高级别的goto

在我所知道的任何语言中,GOTO都不是一等值,因此术语“高级GOTO”没有意义。但是,如果你真的对函数式语言中的流程控制操作感兴趣,你应该研究continuations


我认为你在评价 F# 时有些苛刻,当你说它“对元编程的支持非常差”时。像自定义工作流、引用和在某种程度上类型提供程序等功能可能不是终极元编程工具,但在我看来,它们应该得到比“非常差”的更多评价! - Joh
@Joh: 我不认为自定义工作流程属于元编程。与我描述的其他语言的元编程工具相比,引用甚至类型提供程序仍然很差... Ramon Snir的宏项目与它们类似,这就是为什么它如此酷的原因。 - Mauricio Scheffer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接