Implicit Ports Are Easy, But Are They *TOO* Easy?

I've proposed changing FILE! and URL! to be more "systemically magical", that they are not strings but true proxies for the resource they represent.

But if you take something that can either be a FILE! or a TEXT!, and you think you are doing a textual manipulation with an APPEND... you could wind up writing to the file instead of adding to the string. And this is something that can happen very easily. :-/

For instance:

for-each [file description] [
    %some-file.dat "Random Numbers"
    %unimportant-file.exe "Red 0.6.4" "oops"
    %very-important-file.dat "Photographic Proof of Bigfoot"
][
   append description " (2021)"
]

The stray "oops" caused it to get off by one. Hence instead of appending the date to the description it's corrupted your very important file!

Uh oh.

Are Some Words Not "Alarming" Enough For Implicit I/O?

When something is in a variable and you APPEND to it, it might feel surprising to see that interacting with files and network when no "charged" word like WRITE was being used.

An annoying conclusion one would reach would be that we have to taxonomize words into those which are involved in I/O, and not have them overlap with words we use more casually for manipulation of local state.

This would suggest that the introduction of a plain APPEND port action in R3-Alpha was a mistake. It was from the wrong taxonomy, and needed to at minimum piggy-back on WRITE as WRITE/APPEND so you'd know you weren't looking at a string operation if you were reading:

write/append blackbox data

The less extreme conclusion would be that the limitation only applies to the implicit port actions.

This would draw a distinction between these two functions:

foo: func [value [port! text!]] [... append value data ...]

bar: func [value [file! text!]] [... append value data ...]

The argument here would be that in the former case...the only way to get a PORT! is if you've already gone through one of the taxonomized "I/O words" such as OPEN. You've thus greenlit the idea that remote impacts will happen to that resource. You didn't know when it would happen, but you knew that wherever you passed that port off to had that approval.

But with FILE! as a casual lexical element appearing in lists, you might miss this greenlight step. So the green light has to be baked into the word itself... a word like WRITE.

Are There Other Options for "I/O Greenlighting"?

My proposal on taking the OPEN and CLOSE out of the implicit port process suggested that if you make a port out of something then every action you do on the port would be run implicitly, unless you did some kind of operation (like an OPEN or CLOSE) on that port that would batch the operations up.

This means the creation of the PORT! itself would thus serve your greenlighting act.

var: %foo.txt
append (make port! var) "some data"

Some shorthand for that could be made. We could do like Haskell's notation for "putting things in the IO monad" and say that the decoration IO will MAKE PORT! on something that isn't already a port:

for-each file [%a.txt %b.txt] [
    append (IO file) "some stuff"
]

for-each file [%a.txt %b.txt] [
    append IO file "some stuff"
]

Or you could greenlight through some parameter convention that port actions understand, like giving an @ argument:

var: %foo.txt
append @var "some data"

vars: [%foo.txt %bar.txt]
append @(second vars) "some data"

Though I don't think I like that much...the IO concept is clearer.

Regardless, I Still Think FILE! Should Not Be An ANY-STRING!

What we're encountering here is a safety issue with implicit ports, and I think it would be an overreaction to say "don't do them at all, and let's go back to making APPEND on a file treat it like a string".

Notably the problem only applies when you're using variables or expressions. There's nothing to worry about if the argument is literally a FILE! or a URL!:

append/line %foo.txt "some data"

That feels expressive, not unsafe. The evaluator can tell the difference, and we can say that it's okay when you've given a literal path like that to accept that you know it's going to be I/O.

And I've mentioned that there are words that carry the greenlight in them... with examples like WRITE and READ:

for-each file [%a.txt %b.txt] [
    write file "some stuff"
]

I feel it would be a step back to require you to decorate that. Write means write.

So... is it just a flag on a word, to say there are "IO-greenlit words" and "not-necessarily-IO words"?

Food for thought. What do you think of all this? :-/

Maybe a file should be "immutable" (= read-only) until you don't make it mutable...

There are a lot of technical reasons why const and mutable would sort of become bent into knots by this, and they're details beyond the scope of what you probably want to read. :slight_smile:

So instead I'll just point out that you might want to avoid stray reads too. As it so happens, the network port implemented COPY as a synonym for READ. Why did it do this? I don't know, but it did.

This means that copy var when var is http://example.com/100-gigabyte-file.dat will do a very large read from the network. A stray I/O operation may have much greater costs than a stray local operation...such as using up your data limits.

Plus I think my proposal of using the IO decoration seems more like it means what it says than mutable.

I don't think it seems so bad, as if it's missing it can just say "hey you asked to APPEND to %foo.txt and it was indirect through a variable...if you meant that, say IO".

It seems worth it to have nice quick scripting ways to say append/line %foo.txt "hello" ... and then if you use it in a more abstracted sense you just put the annotation on.

I summarized the point of my above writeup to @BlackATTR like this:

"Implicit ports may look like they could cause scary bugs. But there are mitigations of that problem. And we know append/line %file.txt "hello" is desirable."

"So let's just keep in mind what the desirable thing is and then we'll annotate our way out of the scary."

So although my Bigfoot example might look at first glance like deal-breaker, I think the things I describe afterward show it's not.

Taking FILE! and URL! out of ANY-STRING! is still the right answer. We just might throw in a couple of speedbumps on some implicit port instantiations to force you to be (a little) more explicit.

2 Likes

Another word here that suggests no particular action be taken, -but- that you may be hitting a remote resource, could be LOOKUP.

for-each file [%a.txt %b.txt] [
    append (lookup file) "some stuff"
]

for-each file [%a.txt %b.txt] [
    append lookup file "some stuff"
]

So trying this in practice, it's certainly a very aggressive choice that bumps up against a pre-existing religion in the system.

With FILE! acting so non-stringy, one of the most convenient things to do if you plan on mucking with paths is to turn it into a string. Which seems not so bad.

But...then throughout the system, there's an enforcement that if you have a TEXT! it's not the right type for doing file oriented things with... like making a directory etc.

Remember the other purpose of FILE! was to gloss over backslash and other inconsistencies in Windows vs. Linux. So to the extent TEXT! is accepted by any of the file routines, that's interpreted as a local path...it has the slashes adjusted. So AS TEXT! of a FILE! might give it less of a trigger-finger as far as port actions go, but the backslashes aren't right.

Maybe FILE! Should Stay A String?

This made me start to wonder if FILE! should really just be a string type, and the "implicit port" mechanic only works on file:// URL!s. Then maybe IO could still work on file.

I don't know...I'm going to do a bit more thinking while other things push ahead.