rhombus-prototype icon indicating copy to clipboard operation
rhombus-prototype copied to clipboard

Initial Path integration

Open samdphillips opened this issue 1 year ago • 5 comments

Some initial Path integration that I've been poking at for a couple of weeks now.

Initially, I used a veneer for Path.read_with and Path.write_with that were syntax and would take a block that would become the read (write) procedure. Once I started thinking about adding the other flags for reading and writing the syntax seemed too messy so I went back to a simple wrapper around call-with-input-file/call-with-output-file.

samdphillips avatar Oct 14 '24 21:10 samdphillips

Crazy idea: what if we want RAAI-style with a Closeable.def form?

block:
  Closeable.def in = Port.Input.open_file("x")
  ....
  // `in.close()` is added to the end here

The Closeable.def form could be implemented roughly like this:

namespace Closeable:
  export:
    rename cdef as def
  defn.sequence_macro 'cdef $bind ... = $rhs
                         $body
                         ...':
    values(
      '
        block:
          def mutable resource = #false
          try:
            ~initially:
              resource := $rhs
            block:
              let $bind ... = resource
              $body
              ...
              #void
            ~finally:
              resource.close
      ',
      '')

mflatt avatar Oct 15 '24 12:10 mflatt

The closeable definition is neat; I would probably want it to return the value of the last expression in the body, personally.

benknoble avatar Oct 15 '24 14:10 benknoble

RAII isn't that crazy and I intended to have a more general form. I still forget sometimes that Rhombus macros can see subsequent forms in the same context. I'll take a look at implementing this.

I'm thinking that next we may want to define a Closeable interface that classes can opt into.

I agree with Ben that it might be better to return the last value instead of #void. Also, would using Closeable.defcause the final expression of the block to not be a tail call?

samdphillips avatar Oct 15 '24 17:10 samdphillips

Yes, it should definitely return the value. I needed #void to cover the empty-remainder case, but didn't think through that it should be at the beginning, not the end.

Another issue with this macro implementation is that static info from $rhs should flow to resource. Maybe statiic info will have to be propagated explicitly by Closeable.def, forcing expansion of $rhs and using statinfo_meta.gather.

Note that as written, $rhs will be evaluated with breaks disabled. I think that's ok — anything not meant to be in non-breaking mode can be lifted out — but users of Closeable.def would need to be aware. Disabling breaks was intentional here to ensure that either the resource is closed on escape or it was not allocated (as long as the running thread isn't killed).

I agree about having a Closeable interface that other classes can implement, and maybe Closeable.def should require a Closeable object. The fact that close isn't called as a method above is a bug, of course.

mflatt avatar Oct 15 '24 19:10 mflatt

I've been writing some path-related code recently, so this is helpful. How much of the racket/path library should be exposed as Path methods? Do we want something minimal here, or something that exposes basically all of racket/path (and maybe much of racket/file)?

samth avatar Oct 16 '24 21:10 samth

Added the Closeable interface and Closeable.def syntax. Feedback appreciated.

samdphillips avatar Oct 22 '24 02:10 samdphillips

@samth wrote:

I've been writing some path-related code recently, so this is helpful. How much of the racket/path library should be exposed as Path methods? Do we want something minimal here, or something that exposes basically all of racket/path (and maybe much of racket/file)?

I've looked at what other languages (specifically the Python pathlib) have and slowly added those as I've needed them.

samdphillips avatar Oct 22 '24 02:10 samdphillips

Is there value in keeping Path.read_with and Path.write_with?

samdphillips avatar Oct 22 '24 02:10 samdphillips

I pushed some adjustments to Closeable, since the interplay between Racket and Rhombus layers there is tricky.

mflatt avatar Oct 22 '24 18:10 mflatt

Is there value in keeping Path.read_with and Path.write_with?

I'm in favor of dropping those.

mflatt avatar Oct 22 '24 18:10 mflatt

BesidesCloseable, it looks like to_complete_path is not yet documented, and probably that should be to_absolute_path.

Should AbolutePath and RelativePath be annotations (which are easier to use in patterns)? And then drop Path.is_absolute?

mflatt avatar Oct 22 '24 18:10 mflatt

I added one more operation (Path.only)

samth avatar Oct 22 '24 18:10 samth

Incidentally, here are the current path/file-related racket imports in the file I'm working on:

import lib("racket/base.rkt") as r:
  expose #{link-exists?} as link_exists
  expose #{directory-list} as directory_list
  expose #{rename-file-or-directory} as rename_file_or_directory
import lib("racket/file.rkt") open:
  expose #{write-to-file} as write_to_file
  expose #{make-temporary-file} as make_temporary_file
  expose #{delete-directory/files} as delete_directory_files
  expose #{make-directory*} as make_directory_alt

samth avatar Oct 22 '24 18:10 samth

I added one more operation (Path.only)

It was initially unclear to me what that method is supposed to do. Could we use a different name for that? In the shell that would be similar to the dirname operation. In Python pathlib it's close to Path.parent.

samdphillips avatar Oct 22 '24 19:10 samdphillips

Should AbolutePath and RelativePath be annotations (which are easier to use in patterns)? And then drop Path.is_absolute?

At first I had made those as annotations. But they didn't seem as useful since they were just predicate annotations and I could just use the predicate directly. I could see them being useful in match and with is_a instead of calling the predicate methods.

samdphillips avatar Oct 22 '24 19:10 samdphillips

expose #{make-directory*} as make_directory_alt

~~Path.make_directory_tree sounds better to me.~~. Just realized you were providing feedback, not volunteering :D. These mostly look relevant and I'll see about fitting them in.

samdphillips avatar Oct 22 '24 19:10 samdphillips

I pushed some adjustments to Closeable, since the interplay between Racket and Rhombus layers there is tricky.

I'm not sure if this is good or bad, but in the previous iteration this was a static error

class Bar()

block:
  use_static
  Closeable.def y = Bar()
  #void

samdphillips avatar Oct 22 '24 19:10 samdphillips

I'm not sure if this is good or bad, but in the previous iteration this was a static error

Interesting, but I currently lean toward "bad" on the grounds that it's indirect (checking for close as opposed to Closeable) and more ad hoc than most rules.

mflatt avatar Oct 22 '24 20:10 mflatt

I added add_suffix. I'm happy with either dirname or basename or parent or something else for path-only.

samth avatar Oct 22 '24 20:10 samth

I added add_suffix. I'm happy with either dirname or basename or parent or something else for path-only.

An idea based on Vim (and Zsh) path modifiers, which uses :h and :t for the equivalent of dirname and basename: head and tail. Probably too ML-ish/not clear enough without the connection to other tools, but a neat connection nonetheless.

benknoble avatar Oct 22 '24 23:10 benknoble

BesidesCloseable, it looks like to_complete_path is not yet documented, and probably that should be to_absolute_path.

Should AbolutePath and RelativePath be annotations (which are easier to use in patterns)? And then drop Path.is_absolute?

Both of these are now implemented.

samdphillips avatar Oct 29 '24 20:10 samdphillips

This seems ready to merge. I still have some small thoughts, but I don't want to create too much delay with more questions and further discussion (because we can always change more after), so let's time out these suggestions if they don't seem immediately like a good idea:

  • Change Path.extend to Path.add? That seems easier to remember in combination with +/.
  • Change Path.parts to Path.split? The terminology "elements" is more established than "parts" within Racket, but both "parts" and "elements" seem generic. The verb "split" is more compelling to me, makes clearer that it's a method instead of a field/property (although that's a weak benefit), and fits with a likely String.split to appear.

FWIW, I think some of the path tests will fail on Widows, but we can leave that to later (along with path-kind additions).

mflatt avatar Oct 30 '24 14:10 mflatt