5.3 Input and Output Ports
A port represents a source or destination for reading and writing data. The names stdin, stdout, and stderr access the current input source, output destination, and error-output destination, and they are sometimes used implicitly. For example, print takes an output port as an optional ~out argument, and it uses stdout when ~out is not supplied.
Examples show output written to stderr in red italics.
hello
hello
Ports are objects that satisfy Port and either Port.Input or Port.Output, and they support associated methods. The Port.Output.print method can be used on stdout and stderr, for example, instead of calling the print function.
hello
hello
All ports ultimately read or write bytes, and so the most primitive methods work in terms of byte strings. A byte string is written like a string, but with a # prefix. As a convenience, ports also provide methods for reading and writing strings using an automatic UTF-8 conversion.
The Port.Input.open_file and Port.Output.open_file functions create port objects to read and write to a file.
> def tmp = filesystem.make_temporary()
> def outp = Port.Output.open_file(tmp.path, ~exists: #'truncate)
> outp.write_bytes(#"data")
4
> outp.close()
> tmp.close() // deletes temporary file
In a non-interactive context, using Closeable.let is normally a better choice than directly calling Port.Output.close and similar methods. We return to that form in Closeable Objects.
A port can be used for stdout or stdin by using parameterize with Port.Output.current or Port.Input.current.
> def tmp = filesystem.make_temporary()
> def outp = Port.Output.open_file(tmp.path, ~exists: #'truncate)
> parameterize { Port.Output.current: outp }:
println("data")
> outp.close()
> def inp = Port.Input.open_file(tmp.path)
> parameterize { Port.Input.current: inp }:
"data"
> inp.close()
> tmp.close()
Finally, since the combination of opening, setting stdin or stdout, and then close is so common, it is supported directly by Port.Output.using and Port.Intput.using.
> def tmp = filesystem.make_temporary()
> Port.Output.using ~file tmp.path:
~exists: #'truncate
println("data")
> Port.Input.using ~file tmp.path:
"data"
> tmp.close()
The Port.Output.open_string function creates an output port that accumulates a string, so it can be used to build up large strings where appending would be inconvenient or inefficient. The Port.Output.String.get_string method extracts an accumulated string from the port. The Port.Output.open_string function similar enables reading from a string. String ports can be closed, but since they do not allocate constrained system resources, leaving them completely to garbage collection is fine.
> def outp = Port.Output.open_string()
> outp.print("hello")
> outp.print(" ")
> outp.print("world")
> outp.get_string()
"hello world"
> def inp = Port.Input.open_string("λ")
> inp.read_byte()
206
> inp.read_byte()
187