3 ms·
Note that this explains why string formatting is inherently tied with the I/O layer, but not necessarily why `println` (which is a very specific I/O operation)
by lifthrasiir 5y ago
Note that this explains why string formatting is inherently tied with the I/O layer, but not necessarily why `println` (which is a very specific I/O operation) should be in the `fmt` module. That one is more subjective and probably only for the convenience.
- kragen 5y agoDo you mean why you can write (untested) fmt::println(x, y, z); instead of (also untested) fmt::fprintf(os::stdout, "{}{}{}\n", x, y, z); ? I mean I agree that presumably anything you could do in the first form could be done in the second form, less conveniently as you say, but if you omit println from fmt then all the Golang programmers learning Hare will be surprised about the missing stair they expect as the Hare counterpart to fmt.Println. And I think omitting the terminating newline in your format string is a common enough bug that it's probably worthwhile to include a separate function that adds it implicitly, especially in the case of fmt::print, which doesn't have a format string to add it to. Even without Golang experience, if you're going to have an fprintf and a println, I'd look for the println in the module that has the fprintf in it.
- lifthrasiir 5y agoWell, I just meant `println` should be probably in `io` rather than `fmt`---it is a great convenience function to have. Also not all streams are equal: stdio is heavier than file streams, which in turn are heavier than memory-backed streams (for example, stdio will probably need a built-in lock while string streams needn't). If you only need for example memory-backed streams you want to never see any code related to stdio, which might not be possible in some designs.
- kragen 5y agoYeah, it would be a reasonable design for fmt to contain no direct dependency on io, just on the stream interface. And that would enable you to put print and println (as opposed to fprintf) in io rather than fmt without creating a circular dependency between modules. And it might be a convenient way to remove unneeded dependencies on embedded targets (though I don't think they're targeting those). But it would be a significant departure from the Golang interface. I don't remember if Golang "stdio" has built-in locks. Hare doesn't seem to have threads or locks, o that may be a non-issue.
- ddevault 5y ago"io" cannot depend on "fmt", since "fmt" depends on "io" (technically, dependency cycles in Hare can be solved, but it's messy and strongly discouraged if avoidable). We did previously have a simple io::println which accepted strings but did not do formatting, but it's not super useful and fmt::* does it much better (and it was unbuffered, because bufio depends on io), so we ended up removing it. I thought about keeping it but making it use vectored writes to get around the lack of buffering, but in the end it's not really worth it.
- kragen 5y agoWhat do you mean by "vectored writes"? I thought you meant "invoking a function pointer" but io::stream already does that. Why is io::handle a separate type from io::stream? Is it just an efficiency hack to avoid an indirection through a function pointer for the common case where what you're writing to actually is a file? Can't io::handle eliminate the entanglement between buffered output and users of streams like the old io::println? I feel like there's something I'm not understanding here. (Is fd: int really the right way to handle a pointer to a structure representing how data is being transparently compressed into a zipfile, or a terminal emulator state that is being updated by writing bytes to it?)
- ddevault 5y agoBy vectored writes I mean the writev(2) syscall, which can write from multiple buffers in a single syscall (offsetting the performance loss from unbuffered I/O, in theory). io::handle is separate from io::stream because it needs to store either a stream or an io::file. io::file is necessary because it's required for many operating system constructs - a TCP socket is an io::file and cannot be an io::stream, for instance. Only io::files can be passed into syscalls like poll(2). However, stream is separately useful for building userspace I/O primitives, such as io::tee, cryptographic hashes and streams, and so on. So io::handle allows you to have an I/O object which is either a file descriptor (io::file) or a userspace stream (io::stream), but your code doesn't have to care about which it is.
- kragen 5y ago