opendylan icon indicating copy to clipboard operation
opendylan copied to clipboard

open-file-stream(..., if-does-not-exist: #f)

Open cgay opened this issue 5 years ago • 4 comments

The if-does-not-exist: keyword argument to open-file-stream and make(<file-stream>) is documented to accept #f as a possible value, meaning "no action".

  1. What "no action" means needs to be documented better, but I take it to mean that for direction: #"input" no error should be signaled and #f should be returned, similar to in Common Lisp. For direction: #"output" I don't think "no action" makes much sense.

  2. The unix and win32 accessor-open methods document in comments that #"create" and #"signal" are the only two valid values. (Even though they don't seem to use if-does-not-exist at all!)

  3. direction: #"input", if-does-not-exist: #f signals an error when the file doesn't exist.

cgay avatar Dec 09 '20 02:12 cgay

Maybe direction: #"output", if-does-not-exist: #f is unlikely to be used (only write to the file if it already exists?) but presumably it should do the same as for #"input": just return #f.

I'm just going to throw this in here, since it's somewhat related:

Check what happens for if-does-not-exist: #"error" and other possible misspellings of the symbols #"create" and #"signal". Can we use constants instead to get yummy type safety and compiler warnings? $create and $error would be my choice.

cgay avatar Dec 09 '20 16:12 cgay

Pinging @cgay ... why haven't you fixed this yet?

cgay avatar May 18 '24 15:05 cgay

The main relevant doc is here: https://opendylan.org/library-reference/system/file-system.html#options-when-creating-file-streams

open-file-stream returns a <stream> or signals an error. This seems right and good. Returning #f the way Common Lisp's open function returns nil just adds complexity for callers.

In Common Lisp, with-open-file binds the stream variable to nil and executes the body, expecting the body code to check for nil when :if-does-not-exist nil is used.

In Dylan we make the stream by calling make(<file-stream>, ...) which cannot return #f, so we need to catch <file-does-not-exist> and do something with it.

(1) We could do what Common Lisp does:

with-open-file (stream = "/no/such/file", if-does-not-exist: #f)
  if (stream)
    read-to-end(stream)
  else
    "whatever"
  end
end

The above doesn't work currently if you look at the with-open-file code, because it binds the stream to a variable of type <file-stream>.

(2) We could instead skip executing the body and make this be the Dylan idiom:

with-open-file (stream = "/no/such/file", if-does-not-exist: #f)
  read-to-end(stream)
end | "whatever"

(3) or we could disallow if-does-not-exist: #f altogether, meaning people would have to write this instead:

block ()
  with-open-file (stream = "/no/such/file")
    read-to-end(stream)
  end
exception (<file-does-not-exist-error>)
  "whatever"
end

I personally like the middle option as it's concise and it's what I assumed was meant to happen from the start so at least to me it's the intuitive option.

cgay avatar May 20 '24 03:05 cgay

(1) sort of makes sense in CL because nil is a synonym for stdin/stdout but less so in Dylan.

(2) has the problem that it changes the semantics if the body contains side-effecting code, so we'll forget that idea.

(3) is the least ambiguous and seems to match Dylan style best.

I'll add (4) use something less ambiguous than #f, like #"skip-body".

cgay avatar May 20 '24 18:05 cgay