in Rebol 2, you could say: system/ports/output: open %some/file and that is where PRINT's output would end up
That's the kind of thing I meant, when I said having things in usermode would certainly make that possible...trivially, even. We could go through the step where STDOUT is a port, and defaulted
system/ports/output to that port...then the PRINT code could simply be changed to use WRITE SYSTEM/PORTS/OUTPUT. It would then work as you describe.
(Notably, if you said
system/ports/output: %some/file and did not use
open, then each PRINT statement would rewrite the file. This may or may not raise semantic issues of if WRITE should always assume it's appending to what's there, and you have to delete it beforehand otherwise. Or maybe there would need to be some kind of type trigger that would prevent modifications to
system/ports/output that wasn't a PORT!...? Questions...)
My own wish would be to ergonomically flip it. It seems laborious for the average author to have to say write system/ports/output ">>" to get the indirection. I'd rather they say write stdout ">>" and get the appropriate indirection, shared with whatever PRINT sees.
...but I don't know how to do that with your assignment model, because saying stdout: xxx can't currently change the stdout that the PRINT in the lib context sees. You have to say at minimum sys/stdout: xxx to get them on the same page of changing the same context. It changes if you say stdout/set-target xxx which then gives you the ability to change a property of a common object.
If it would make you happy enough to code more for now, we can copy Rebol2's model...and it can be how things are done until something better comes along. I don't have a problem changing PRINT to WRITE to
system/ports/output and make STDOUT a PORT! that's the default place it writes to. Then add STDERR that similarly defaults to
We would make WRITE-STDOUT a specialization of WRITE to
system/ports/output so it saves some typing. That could compromise between what I'd like to see and getting the indirection, and would be no worse than what's there today. Though that notably brings out the example of a specialization that does not want to capture a value... since you want it to see changes. You'd have to write it as:
write-stdout: adapt (specialize 'write [destination: <to-be-overwritten>]) [
So the specialization removes the parameter and sets it to garbage, then the adaptation injects prelude code on each run to fetch another value. This "dynamic specialization" pattern is something I've been looking to see how to make easier. But...at least it is possible.