Sounds like we're saying the same thing, no? When I was talking about thread safety, I didn't mean to imply that the low-level synchronization Object API affect immutability of an object. I was referring to the idea that File state, such as exists() is rather low-level, such that it should be stuffed down into implementation details of one's architecture. In Java, it feels most natural to not force a square peg into a round hole and instead put that kind of logic (persistence, crazy mixed up API like Date/Calendar, etc.) behind the veneer of functional and immutable style so as to expose state to the rest of your system or client code in the way of your choosing, best suited to the chosen architecture.
Alexey ________________________________ From: Kevin Wright <[email protected]> To: [email protected] Cc: Alexey Zinger <[email protected]> Sent: Mon, March 14, 2011 6:04:30 AM Subject: Re: [The Java Posse] Some design questions (about immutability and other stuff) What a tangled web we weave... There's an awful lot going on here, possibly best to get the fluff out of the way first to clear the "path" :) First; wait, notify, notifyAll don't matter in the slightest. Conceptually, these can be treated as though they didn't even exist, just pretend that they're static methods on some other class that take an object instance as their first argument. An immutable object has no business dealing with locks, semaphores and other synchronisation constructructs, and if another piece of code happens to wait on some immutable instance `foo`, then it really doesn't affect `foo`s internal behaviour whatsoever, a total red herring. More disturbingly, final isn't actually immutable! A final member can still be modified via reflection, native code or some form of bytecode manipulation. So if you're desparate to find ways that JVM immutability can be broken then there's no need to mention wait/notify/etc. as there's a far more fundamental issue to hand. Immutability can *always* be broken given enough willfull vandalism. It's probably best to just mark this down as risky behaviour - if you want to try it then you're on your own - in any sane normal usage it's completely reasonable to treat `final` as though it truly does mean "immutable", I try not to lose any sleep over it. Incidentally, it's also reasonable for an immutable class to have a mutable subclass, this is also not a problem, but it does raise some questions about having an inheritable @immutable annotation... As Reinier stated, there are some very deep issues when it comes to using @immutable as anything more than a hint. Short of an effect-tracking system in the compiler, there's little more we can do. Convention has to rule the day here. So... Files! The current Java implementation is so convoluted that there's nothing for it but to go back to first principles, sacrificing even object orientation if necessary to get to the truth of the matter (it can always be added back later). Your chances of teasing any immutability out of the current class hierarchy are about as likely as winning the lottery. However you look at it, things have to be changed around quite a bit to get this whole immutability business right. So let me take you on a journey, if I may... --------------------- What is a file? It's more than just a path, you can have a well formed path that refers to a non-existant file. It's not just a sequence of bytes on a storage medium, it also involves other metadata - such as a path, modification timestamp, etc. Then we have "files" under procfs on linux systems, and named pipes, and network streams - all accessible fia "file handles". There are different and deeply interwoven concepts that are all known as "file" in different contexts. Some of these can be immutable. First, there's the path. This is completely immutable. Paths can have many operations, you can get the parent path, find the path of the some subdirectory(as a new immutable path instance), etc. Path needn't have an operation to determine if it's well-formed, as this can be done at construction time. In the face of named pipes et al, paths should really just be represented as uris. Path can then be introduced as a subclass of Uri, with an additional method to return the "native" representation. There are also functions that operate on paths, but don't posess referential integrity, such as determining if a file exists at some path. Because side effects are involved, it's not valid for such functions to be methods on paths. The next layer is some sort of File reference (or handle, moniker, etc. I've seen them all used before). I favour FileRef because it's concise and clear in meaning. A FileRef *might* be immutable, though often isn't, a good copy-on-write filesystem would also be able to provide immutable FileRefs. To go from a Uri to a FileRef, there needs to be a factory method capable of returning different FileRef subclasses. One such subclass would be NonExistentFileRef (which is immutable), another such subclass would be DirectoryRef. Given that we're using URIs in lieu of paths, a FileRef could also represent a web page, remote FTP file, named pipe, etc. It's doubtful that "file" ref is even a good name here, but it's historic and has a lot of stucking power. As FileRefs can be immutable, they also mustn't contain methods that can't be free from side-effects. So `delete` won't be an available operation - it has to be a separate function. All you can do with a FileRef is obtain the path and metadata, and open the contents. In the case of a directory, the contents would be a collection of contained FileRefs, for a network connection the contents would be a read-once stream, for a "standard" file they could be a stream or random-access buffer. The exact subclass of FileRef (maybe combined with traits or interfaces) then determines exactly what form of "contents" are available. To ensure that COW FileRefs are immutable, writing/modifying would then have to be done via function that transforms one instance of file contents to another instance. This function is then passed to a method on FileRef that applies the translation and returns the modified FileRef instance. DCI isn't necessary here, as all the relevant differences can be captured in the subclass hierarchy of FileRef. This also neatly sidesteps any considerations about equality on roles :) As for methods like `delete` or `rename`... they'd need to go in a utility class of static methods or a singleton. I'd call it `FileSystem` On 13 March 2011 21:09, Alexey Zinger <[email protected]> wrote: File systems -- and I/O in general -- tend to throw a big monkey wrench into the whole immutability conversation. In some ways, this is where theory and reality of computing collide and some abstractions start to leak. I suggest sometimes it's rather pointless to choice immutability to the bitter end if there is no real tangible benefit. While we can agree that there are many design flaws in java.io in general, I think there's only so much designing that can be done around the fact that we need to be able to pass around references to entities that describe some persistence or I/O artifacts, and at the same time, by their nature, they exist with the purpose of having side-effects. > >In situations like this, I usually don't agonize over File and streams and so >on, but design some architecture around the bigger concepts of persistence >itself and allow my framework to expose its state in thread-safe manner as >needed. This lets me pass around some abstractions that have very well >defined >side-effects in a way that makes them "functional-friendly", while under the >hood my persistence framework might look a lot more procedural with fairly >traditional flow of control and synchronization patterns. > > Alexey >2001 Honda CBR600F4i (CCS) >1998 Honda RS125 (CCS) >2002 Suzuki Bandit 1200S >http://azinger.blogspot.com >http://bsheet.sourceforge.net >http://wcollage.sourceforge.net > > > > > > ________________________________ From: Fabrizio Giudici <[email protected]> >To: The Java Posse <[email protected]> >Sent: Sun, March 13, 2011 11:44:28 AM >Subject: [The Java Posse] Some design questions (about immutability and other >stuff) > > >A parallel discussion at the Lombok mailing list revamped some thoughts and >doubts about immutability. I'd like have some feedback about it. > >Given that immutability is a "turtles all the way down" thing ("turtleness" in >the following), a poster raised doubts about things such as File. File is >immutable for what concerns its internal state (the path), but all methods >that >actually work on the related file don't always return the same values (or do >the >same thing) because they refer to the actual file which is mutable. > >Such a class is regarded as mutable or immutable? Of course, I'm interested in >the practical purposes. > >Curiously, the "anemic object" antipattern is something that would help here. >If >you defined File only as a wrapper on a path, and FileSystem another class >which >accepts a File as argument, you would have clearly separated the mutable and >immutable portions. > >But I don't like anemic objects. When I can, I just separate the two parts in >two different objects, but they stay bound together. Referring to some actual >code that I'm not using in production but for some tutorial, I have a: > >FileModel fileModel = new FileModel("/foo/bar"); >String path = fileModel.getPath(); >String name = fileModel.getName(); > >and the above two methods are the only two specific methods of FileModel. So >far, it's anemic and immutable. Going on: > >fileModel.as(Removable).remove(); >long size = fileModel.as(SizedInBytes).getSize(); >List<FileModel> children = fileModel.as(Composite).findChildren().results(); > > >That as() is my way to deal with DCI and Semantic models. In simple words, it >just recovers a "role" of FileModel by referring to its interface - >as(Removable) is a shortcut for as(Removable.class). All the roles in the >example above are simple, stateless objects that work on the real filesystem. >Clearly, I've separated behaviours: FileModel on its own encapsulates the >really >immutable portion, while roles keep the remainder behaviours. But there's no >turtleness in FileModel, because there's only a single level of turtles (i.e., >fileModel.as() always returns the same thing for each role, but the returned >object is not immutable in the strict definition). > >There are variations on the theme. Some roles are de facto hardwired in >FileModel; others can be dinamically injected by the context. But once a role >has been injected into a FileModel instance, it stays forever. > >How do you comment about immutableness of FileModel? As said, there's no >turtleness. It's enough to say that FileModel is not immutable? > > >PS Another related question is about equals() and hashCode()... Are two >FileModel instances with the same path, but possibly different roles, >equals()? >I'd say not. But this is another question, and more related of the broken >definition of equality in Java. > >-- Fabrizio Giudici - Java Architect, Project Manager >Tidalwave s.a.s. - "We make Java work. Everywhere." >java.net/blog/fabriziogiudici - www.tidalwave.it/people >[email protected] > >-- You received this message because you are subscribed to the Google Groups >"The Java Posse" group. >To post to this group, send email to [email protected]. >To unsubscribe from this group, send email to >[email protected]. >For more options, visit this group at >http://groups.google.com/group/javaposse?hl=en. > > > > -- >You received this message because you are subscribed to the Google Groups "The >Java Posse" group. >To post to this group, send email to [email protected]. >To unsubscribe from this group, send email to >[email protected]. >For more options, visit this group at >http://groups.google.com/group/javaposse?hl=en. > -- Kevin Wright gtalk / msn : [email protected] mail: [email protected] vibe / skype: kev.lee.wright quora: http://www.quora.com/Kevin-Wright twitter: @thecoda "My point today is that, if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent": the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger" ~ Dijkstra -- You received this message because you are subscribed to the Google Groups "The Java Posse" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/javaposse?hl=en.
