> Consider now you send a physical letter to me? You use a mail carrier. You have my address, but sending is through the carrier. Same for calls, you use a service provider to make the call.
We perform some actions with tools, but not all. Do you use a tool to run? To read a book? You simply run, or read. My issue is that Go forces every situation into the tool paradigm.
If I have an object representing a file that I can read from, I am reading from that file. I am not using anything to read from it. So either the object can no longer represent the file directly (but instead a tool used to read it), or the file can keep representing the file but the language is off because the file is now also a "Reader" (in the Go sense) of the file.
> You are using some other code capable of reading from the file, though.
We are talking about how to conceptually name pieces of our code to match our natural concepts and language. That is, the conversation we're having is one about modeling, so this move seems illegal to me. The "other code implementing reader" is not part of the model. It would be like arguing, in regular life, that "you can't say that you read a book, because really it is your brain doing the reading".
But you are playing into it? If you are the one running, you are a runner. If you used someone else to run a letter to the whatever, they were the runner.
It can fail if you are writing the physical contact point to the hardware, but very few of us do that. We use code that does that.
Do I use a tool to read? I have reading glasses. And an ebook reader. And I've delegated some reading to others. Life is complicated. Language no less so.
Your ebook reader it the thing being read, not the thing that reads. Glasses for reading are also called readers, and they are the tool helping you read. Here, to your point, we see "language being complicated" -- the same convention being used for opposite things. But this is a tangential point leading us astray.
I thought about this some more in the shower, and I think I can distill my objection more clearly.
Methods on objects act on those objects. That is:
object.do()
is equivalent, in English, to performing the action "do" on "object". To take a random example from Effective Go:
We "Stat()" the file (get statistics about it) and we "Close()" the file. This pattern is well-established in both Go and nearly every language with objects. Instance methods are actions performed on their object.
Now of course "File" also implements "Reader". Yet when I "f.Read()" the situation is exactly the same as in "Stat" and "Close". I am reading the file.
So where is the "Reader" now? Well the Reader is also the file. In the conceptual model, there is no other object around. The file is both the Reader and the object of reading. And that's my problem. The "reader as tool" is inconsistent with "methods act on objects".
In the tool model you've been arguing for, I would have to write something like:
reader.read(file)
The redundancy of that aside, this is not typically what you see with Go code (though you sometimes do). You are more likely to see stuff like "f.Read()"
As I said, you can argue that Go's "File" object is not meant to represent a file, but instead an abstract tool: a thing which can Read and Stat and Close files.
But that's not what the documentation says: "File represents an open file descriptor."
That is, in the intended conceptual model, the "f" in "f.Read()" is a file. And we are reading it. And that is at odds with it being a "Reader" -- a thing that reads.
If I ask you to tell me how Moby Dick starts, you may open an ebook reader, point it to a Moby Dick ebook which it will read for you and display as text on a screen, then you will read that text out loud. To me, you are then my Moby Dick reader. I have not read anything, I used a reader to give me a piece of Moby Dick.
The model is similar with Go's interfaces. You can use a FileReader to read from a file on disk and give you the data. You didn't read the data yourself, the Reader read it for you and put it in a []byte.
Interfaces are always named for what purpose they serve to others, not for what they do internally.
I do agree that the File name is misleading for what os.Open actually returns. Java's FileStream name seems to capture the concept more clearly to me. However, I think this is am intentional choice on the part of Go designers, who are somewhat allergic to object-oriented design. This is the same reason they don't want the receiver of a method to be named "this", even though that is exactly what the code models.
Basically, the Go designers want to pretend that Go struts are not objects, they are only data, and that methods on that data are kinda just free-floating functions with some extra syntax sugar. Of course, that is not what Go actually does - a Go struct instance with methods and private fields is exactly equivalent to a Java Object instance and completely different from a C struct instance. But they don't like that framing.
You'll see this all over the Go stdlib - struct are named after the data they represent, but then they also implement interfaces named for the purpose they serve to others.
So what is an instance of the struct named File? Go has chosen a dualistic perspective, like with the particle/wave dualism in physics. In some experiments, a File is just a representation of an open file, that you pass to other methods to do something with (e.g. ioutil.ReadFile(file)). In other experiments, it is an object which can read data from the OS and return it to you (e.g. file.Read(), or ioutil.ReadAll()).
I think this is an excellent summary and I agree with all of it.
> Go has chosen a dualistic perspective, like with the particle/wave dualism in physics.
This summarizes my complaint. It's not consistent. It forces me to switch between perspectives and keep two contradictory models in my head. As I said, it's not the worst thing -- I can deal with it. But I consider it a kind of conceptual wart in the design, or at least of the naming conventions and idioms.
A final pedantic point. I find your interpretation of "ebook reader" interesting, and grant that it is a valid one, and fits with the Go "thing as tool" perspective. However, I don't believe it is the perspective most people use in everyday life, and I think the dictionary definition makes this clear:
e-reader (noun) -- a portable electronic device used for reading books and other text materials that are in digital form.
That is, and e-reader is the thing, the object, the book-substitute that you read.
EDIT: on 2nd thought, maybe that definition does back up your interpretation. "device used for reading books..." I still feel like in practice it is modeled in people's minds just like a "different kind of book" but maybe that is just me, or just some people....
EDIT 2:
> In other experiments, it is an object which can read data from the OS and return it to you (e.g. file.Read()...)
My other objection here (as I pointed out elsewhere) is that if I am supposed to take the "interface as tool" perspective when interpreting the "Reader.Read()" method, then the variable "file" is misnamed. I read a file, I don't use a file as a reader to read a file.
Since I can only name a variable according to one perspective, the name will always feel wrong when I switch to the other.
The reader reads the file into a string. You then access or modify the string. The file is a thing on disk being read by a file reader, an implementation of the reader interface, which reads the file.
In this case, isn't it kindof right though? If it says it represents a file descriptor, not a concrete file. The descriptor reads the file for you. You close the descriptor, not the file, etc. Though in that case you could argue the name of the object should be FileDescriptor and not File. You are onto something, often we model functions to act on objects, not objects to be tools acting on other objects.
In typical OS definitions, a file descriptor or file handle doesn't do anything, it's just some abstract way of referring to a position in a file uniquely. You can ask the OS to read data from the position in the file that the file descriptor points to, or to write data to it, or to map a segment of it into your memory, or to tell you other details about the file. But the file reader, and the file writer, and the file mapper etc are all parts of the OS, not the file nor the file descriptor.
In this sense, the GP is right - to respect OS terminology, in Go we should write
fileReaderWriter := os.Open(path, "rw")
Since os.Open doesn't return a file handle/descriptor, but an object capable of reading or writing to a file. This is different from C, where open() does return a FILE*, which is just a handle to a file which you explicitly pass to read() or write() or mmap() etc.
You read the from the file descriptor into the buffer. Just like in Go you read from a file into a byte array. Neither the file descriptor nor the file is conceptualized as a tool for reading. The file descriptor is merely an abstraction of the file, which extends the concept of "file" to include pipes, sockets, and other io.
We perform some actions with tools, but not all. Do you use a tool to run? To read a book? You simply run, or read. My issue is that Go forces every situation into the tool paradigm.
If I have an object representing a file that I can read from, I am reading from that file. I am not using anything to read from it. So either the object can no longer represent the file directly (but instead a tool used to read it), or the file can keep representing the file but the language is off because the file is now also a "Reader" (in the Go sense) of the file.
> You are using some other code capable of reading from the file, though.
We are talking about how to conceptually name pieces of our code to match our natural concepts and language. That is, the conversation we're having is one about modeling, so this move seems illegal to me. The "other code implementing reader" is not part of the model. It would be like arguing, in regular life, that "you can't say that you read a book, because really it is your brain doing the reading".