til-lang / til

An easy to extend command language
56 stars 5 forks source link

Highly generic support for nestable "context managers" (Python has "with a, b, c:") #7

Closed dumblob closed 2 years ago

dumblob commented 3 years ago

Before trying to carve out some special built-in syntax or commands or whatever, it's usually good to try to think the problem in terms of nested context managers. It appears many problems from our world can be modeled like that (incl. error handling, etc.).

I don't have any specific command nor syntax in mind, but it'd make sense to give it a thought before diving into extending the language. Maybe the language grammar/spec could stay quite minimal thanks to this.

I'm also thinking about implementing context managers with compile-time macros. But that's all for discussion.

cleberzavadniak commented 3 years ago

I love context managers and even prefer them over "defer" keywords. But they also has its own disadvantages, specially nesting two or three of them (in Python you can use a comma-separated syntax, but still that consumes a lot of horizontal space).

I was discussing that another day with some friends, but we couldn't come to any conclusion on the subject. I don´t want to force developers to indent more their code, you know, or even end procedures with sequences like }}}}} (but in the end I think there's no escape...).

One implementation idea is to be smart with names, like matching file.open with file.close instead of taking the Python approach of having an object containing and defining everything.

with (file.open "/etc/hostname") as f {
 do_something $f
}

The with command already has the result of file.open and in the end pass it as argument to file.close. That would work fine, but still has the }}} potential problem.

The real problem here is: I don't think it's okay to have a command inside a SimpleList. That just doesn't feel right. Ideally, commands for not immediate execution should live inside SubLists, but in this case it's important to have only one being called and easily identifiable.

Maybe it's the case to wait for that syntax that allow creating and passing around Commands. Or, in the worst scenario, simply say with f being file.open "/etc/hostname", as ugly as that may be...

dumblob commented 3 years ago

Many (about 30%) of with ... places in my Python code deals with more than one context managers. This should be kept in mind when designing the syntax.

Btw. how about combining defer + with into one command. Basically a "predefined defer" like:

set @f (file.open "/etc/hostname")
do_something $f

With the meaning: set variable f to a file handle and install (@ prefix) the handle's __defer (__exit__() in Python terms) method to the end of the current scope.

Or something like that (yeah, set seems to become the ultimate tool for everything :wink: - we should carefully design it's syntax to be extensible for future types & use cases).

cleberzavadniak commented 3 years ago

One idea is:

proc read_file (path) {
  set f [scoped file.open $path]
  ...
   return $content
}

So scoped (a regular command) is able to analyze the command being passed to it and automatically call the opposite one (file.close) when the current scope is gone.

(For a more "traditional" context manager, it's probably better to create some kind of "local scope" command that simulates the behavior of starting and closing a new one.)

set path "/etc/os-release"
context {
  set f [scoped file.open $path]
  set content [read $f]
}
io.out $f
# closed file handler
io.out $content
# The file content
dumblob commented 3 years ago

Perfect, scoped and context sound good to me. Thanks!

dumblob commented 3 years ago

Btw. scoped could make things easier if it optionally accepted a "guard" (as Dao does) - it saves lots of boilerplate and makes the code more readable.

cleberzavadniak commented 2 years ago

Well, now we have both scope and autoclose, and they work fine, so I'm closing this issue.

dumblob commented 2 years ago

Great!