How do you implement goto in F#?

点点圈 提交于 2019-12-05 06:36:00

A label has a lot in common with a function: they both act as entry points to some code to be executed. Given that similarity, you can do the following:

let goto f = f()

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

One minor drawback is having to wrap all your code in closures. I don't know--doesn't seem too bad for getting something as handy as goto.

You can get the behavior of a GOTO in F# with mutually recursive functions. Tail call optimization allows for this goto nature and doesn't push anything onto the stack.

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

Here do_read, do_shift and re_reduce act as labels.

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

Code source http://sharp-gamedev.blogspot.com/2011/08/forgotten-control-flow-construct.html

Unfortunately, if I undertstand the compiler errors correctly, I can't use this same syntax in F#. So, since it doesn't seem to be supported natively, how can I implement the goto command in F#?

As Daniel said, a label and its following instruction block can be translated into a function and its body. Then each goto becomes a function call. You must pass all local variables as arguments because separate functions have separate scopes and you must add fall-through calls from one instruction block to the next when necessary. However, tail calls are a more general concept.

Your start loop example becomes:

let rec start () =  // .start
  start()           // goto start

Note that a decent compiler will actually compile this equivalent high-level code back down to jump/branch between instruction blocks in assembler. The main difference is that the stack frames must be reorganised because you can tail call between completely dissimilar environments.

Further, I feel that F#, as one of the languages in the functional programming paradigm, should be able to support higher-level gotos: where you can pass gotos to gotos.

Yes indeed. You cannot pass labels around in other languages but you can pass functions around in F#, both as arguments in function calls and as return values from functions. Other languages, like Fortran, do offer computed goto as a half-way house.

Note that asynchronous programming is an important practical application of this technique. When you call an asynchronous function you tell it where it is to branch to when it completes. For example, when you make a call to start an web page downloading asynchronously you pass it a function that it will invoke once the data is available (essentially, the hardware interrupt received when the last of your data comes in ends up firing off your high-level managed code to operate on the fresh data, which is pretty cool). Modern languages give you the tools to write high-level reusable asynchronous code by combining these goto-like techniques with extra code generation during compilation. In other languages like C# you are screwed because you want to wrap multiple asynchronous calls in a single try..catch but you cannot because they are actually spread across many different functions.

One approach that is not mentioned in the other answers is to create your own computation builder. I wrote two articles that implement some imperative features for F# in a computation builder imperative { .. } (read the first one and the second one).

They don't go as far as to implement goto, but they implement continue and break. You could add support for goto, but you could only jump back to the labels that were executed before. Here is an example using continue:

imperative { 
  for x in 1 .. 10 do 
    if (x % 3 = 0) then do! continue
    printfn "number = %d" x }

If you take a look at the source code, you'll find that goto.js is implemented as a textual preprocessor of the code. It uses regular expressions to find and replace labels and gotos with proper Javascript constructs.

You can use this approach to extend any language (including F# of course). In .NET you could probably use T4 in a similar way. However, manipulating a language at the textual level is usually more of a hack than a proper extension (goto.js's author himself says "Seriously. Never use this."), this kind of metaprogramming is often instead done by hooking into the language AST.

Surely, F# is a powerful enough language to implement a feature as simple as this.

F# actually has very poor support for metaprogramming. Languages with good metaprogramming capabilities include any Lisp, OCaml (via campl4), Haskell (via Template Haskell), Nemerle, Scala, Boo. Traceur implements proper javascript AST metaprogramming capabilities. But AFAIK there's nothing like this for F# yet.

should be able to support higher-level gotos

GOTOs aren't first-class values in any language I know, so the term "higher-level GOTO" doesn't make sense. However if you're really interested in flow control manipulation in functional languages you should look into continuations.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!