risor-io / risor

Fast and flexible scripting for Go developers and DevOps.
https://risor.io
Apache License 2.0
581 stars 24 forks source link

Add `defer` keyword #160

Closed applejag closed 6 months ago

applejag commented 6 months ago

Another parity keyword added from Go.

I suggest the exactly same semantics, meaning:

Edge cases

Outside any function

When called in the main file outside any function, the deferred call should be executed at the end of the entire program.

// example-1.risor

defer print("world")

print("Hello")
$ risor example-1.risor
Hello
world

Use in REPL

When used in the Risor REPL, it should be called when exiting. If it isn't already, the REPL needs to start listening for Ctrl+C and react to it, instead of just exiting.

$ risor
Risor

>>> defer print("hello world")
>>> ^C
hello world

Inside imports

To reduce hidden behavior, when called outside any function inside an imported file, it should be called at the end of import.

// otherfile.risor

defer print("deferred print inside otherfile.risor")

print("print inside otherfile.risor")
// example-2.risor

import otherfile

print("print inside example-2.risor")
$ risor example-2.risor
print inside otherfile.risor
deferred print inside otherfile.risor
print inside example-2.risor

Motivation

Useful when working with io.Closer types, such as os.create():

f := os.create("foo.txt")
defer f.close()

f.write("hello world")

Alternatives

Just to get some perspective. I'm personally in favor with the proposal I made above.

Python with statement

Python's with statement is quite enticing, but I've a personal reluctant feeling for it just because of the extra code block, and the fact that the variable name is at the end.

On the other hand, a benefit is that you get full control over when the scope ends.

with open("foo.txt", "w") as f:
  f.write("hello world")

With Risor, it would probably look something like this:

with f := os.create("foo") {
  f.write("hello world")
}

Downside is that you can then only use this on io.Closer types.

C# using statement

C# has a using var ... declaration that is a simplified using () { } statement. It calls Dispose() on the object at the end of the block.

The nice thing about this shorter version is that it doesn't add extra indentation, similar to Go's defer keyword.

void Foo() {
  using var file = File.Create("foo.txt");

  var text = new UTF8Encoding(true).GetBytes("hello world");
  file.Write(text, 0, text.Length);
}

In Risor, it could look something like this:

using f := os.create("foo.txt")

f.write("hello world")