Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I am looking for an I/O model, in any programming language, that is generic and type safe.

By genericity, I mean there should not be separate functions for performing the same operations on different devices (read_file, read_socket, read_terminal). Instead, a single read operation works on all read-able devices, a single write operation works on all write-able devices, and so on.

By type safety, I mean operations that do not make sense should not even be expressible in first place. Using the read operation on a non-read-able device ought to cause a type error at compile time, similarly for using the write operation on a non-write-able device, and so on.

Is there any generic and type safe I/O model?

share|improve this question
1  
"Beauty is in the eye of the beholder" and is subjective to the viewer and not a good fit for the Stack Exchange Q&A model. –  World Engineer Jun 16 '13 at 3:42
    
@WorldEngineer: If you read carefully into it, basically I was asking for a type safe I/O model, where the type system can be used to enforce that no operation that does not make sense can be even attempted. –  Eduardo León Jun 16 '13 at 3:58
    
@EduardoLeón - Well if you what you want is a type-safe model, ask about a type-safe model. Using words like "beautiful" is a distraction. (Adding the tag "close-me" would have been almost as effective ...) –  Stephen C Jun 16 '13 at 4:13
1  
But to answer your question, an API that made seeking on a non-seekable file descriptor a compile time error would probably be difficult to use ... and impossible to implement. (For example, on Linux the only reliable way to tell if an fd is seekable is to try to seek.) –  Stephen C Jun 16 '13 at 4:24
3  
Haskell is also close to this. Especially if you use conduits or pipes –  jozefg Jun 16 '13 at 13:08

3 Answers 3

Having thought some more about this, I think that it can be proved that it is not possible to deal with seekability entirely by compile time type checking.

  • In Linux / Unix there are things called "named sockets" that exist in the file system and can be opened in the same way as other objects in the file system. The catch is that they are no seekable. And there are "device files" that have the same property; e.g. "/dev/tty" or "/dev/null".

  • An IO library needs to be able to create an input or output stream for an object using a filename or pathname supplied as a string; i.e. a runtime value. (If you can't do that, the language has "limited utility".)

  • The "open" method therefore needs to be able to cope with the cases that it could be asked to open a seekable object or a non-seekable object ... and that it won't know which is the case until it has tried it.

  • This means that even if you were to design a set of APIs that distinguished between seekable and non-seekable streams, there would still need to be a runtime test to determine if a specific file could be opened as a seekable (or non-seekable) stream.

In short, it is not possible deal with this entirely via static typing.

UPDATE

Mat Fennwick commented thus:

Perhaps I'm missing something, but it seems to me that your example could be handled with an algebraic sum type, à la Haskell. Is that not the case?

Yes, I think that you could express different flavours of stream using algebraic sum types. But I don't see how this is going to give you a compilation error if you:

  1. open a stream based on a pathname, then
  2. try to do a seek on a non-seekable stream.

You'd end up having to do a switch to discriminate the seekable and non-seekable cases, and deal with the possibility that you've got the wrong kind of stream.


Now you could design the APIs to minimize the runtime checking. But my feeling is that that comes at a cost. And the cost is that the classes that use the APIs get more complicated. For instance, whenever you pass a stream object you have to think whether you need to declare the method to require a seekable or non-seekable stream parameter. (And you can't just say "either" all of the time and use a common supertype ... because then you need a typecast at the point where the stream needs to be seekable and your compiler can no longer detect the real problem.)

The other problem with mapping (effectively) runtime properties to compile time types / interfaces is that it gets messy when you've got multiple; e.g. imagine if you had types / interfaces for seekability, readability, writability, deletability and so on. You end up with a combinatorial explosion of the leaf classes.

Finally, if this was a good idea, you would have thought that some would have tried it, shown it to be a good idea, and convinced someone to add it to a mainstream programming language. AFAIK, the last step hasn't happened. OK, that is not proof that it won't work ... but the absence is pretty convincing.

share|improve this answer
    
Perhaps I'm missing something, but it seems to me that your example could be handled with an algebraic sum type, à la Haskell. Is that not the case? –  Matt Fenwick Jul 2 '13 at 17:11
    
Hi Stephen, thanks for answering my comment. However, I'm still not sure that it shows that algebraic sum types won't do the trick. –  Matt Fenwick Jul 9 '13 at 20:58
    
@MattFenwick: The problem with algebraic sum types is that, if you have a sum with, say, 3 cases (say, read-only, write-only, read/write), you cannot statically rule out one case (say, write-only) and take the remaining two (say, because you only need to write to the stream). A type-class-based approach would be needed, where the type classes act as type-level "decorators". –  Eduardo León Jul 30 '13 at 15:26
    
@EduardoLeón sorry, I don't understand your comment. It reads as "if you use sum types incorrectly, they won't work". –  Matt Fenwick Aug 5 '13 at 13:48
    
@MattFenwick: What would your sum type look like, then? –  Eduardo León Aug 5 '13 at 13:54

Java has "stream readers/writes", "byte readers/writes", "line readers/writes" that read from and write to "streams". A "stream" can be a file, a socket, an url, etc.

Also the language is type safe, strictly typed, compiled, etc. It yells at compile time if you attempt to do the wrong operation on the wrong type of stream.

share|improve this answer
    
1. Is there such a thing as a stream that is both readable and writeable? 2. If so, can I use a R/W stream when a R-only or W-only stream is expected? 3. Can I distinguish seekable streams from nonseekable ones? (This could be useful, say, if I want to guarantee that a parser performs no backtracking.) –  Eduardo León Jul 30 '13 at 15:16

You could implement something like this using c++ templates. Boost has a lot of examples of limiting behavior at compile time (eg the graph library). The main hurdle to overcome is the syntax of templates.

share|improve this answer
    
I know technically I could roll my own, but I wanted to avoid reinventing the wheel, especially when I am interested in making cars, not wheels. Namely: math.mit.edu/~dspivak/informatics/talks/CTDBIntroductoryTalk (I have been furiously googling for more than two months whether there was any DBMS like that, and there seems to be none, so I am rolling my own.) –  Eduardo León Jun 20 '13 at 3:16

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.