6 ms·
I am really curious why “println” lives in “fmt” module. I get that it does string formatting, but it also prints, and I would expect “fmt” to only do formattin
by xixixao 4y ago
I am really curious why “println” lives in “fmt” module. I get that it does string formatting, but it also prints, and I would expect “fmt” to only do formatting. This sours my first impression of the language. It makes me expect lots of other counterintuitive decisions in the language.
- emersion 4y agoFWIW, Go's fmt package does the same: https://pkg.go.dev/fmt https://pkg.go.dev/fmt
- zxwrt 4y agolink without tracking: https://godocs.io/fmt https://godocs.io/fmt
- kragen 4y agoIf you have a formatting operation that outputs some number of chunks of text to a stream, using some dynamically dispatched output operation which does something similar to writing to a buffered output file, you can use it to produce a string fairly easily, as long as it fits in memory: you implement an output operation that appends to a string buffer. This is close to optimally efficient except when appending characters one or two at a time, in which case the extra dynamic dispatch overhead may be significant compared to whatever else you're doing to choose those characters. If you have a formatting operation that produces an in-memory string, you cannot use it if the amount of text being produced is too large, or if you do not know if it is too large. But if it is not too large, you can then output the string to a buffered output file, although this is less efficient because it involves an extra copy and potentially dirties a lot more memory. So these two ways of handling formatting are easily interconvertible when memory permits, but in some circumstances only the first one is applicable, and it is always more efficient. Therefore, when reliability or efficiency is important, generating a formatted string should be implemented as a layer on top of sending formatted output to a stream, not vice versa. There are cases where reliability and efficiency are not important, because your code is being used by end users and not as part of a library, and an error message is also an acceptable result of trying to run your program, and in those cases you should probably write your program in a language like Lua, Python, or JS. It is probably true that if you are used to languages like those, a lot of design decisions that are necessary for reliability and efficiency will be counterintuitive to you.
- lifthrasiir 4y agoNote 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 4y 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 4y 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 4y 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.