I see a small problem with the Scala stuff. Specifically:
FileRef
- DirectoryFileRef
- StreamableFileRef
- AppendableFileRef
- RandomAccessFileRef
- NonExistantFileRef
How does this address the scenario of a non-existent file reference "becoming"
existent by a side-effect of code elsewhere (maybe outside current execution
thread, or the JVM as a whole)? But in general, this just feels like a losing
battle to me -- trying to apply immutability principles to I/O. Why?
Alexey
________________________________
From: Kevin Wright <[email protected]>
To: Fabrizio Giudici <[email protected]>
Cc: [email protected]; Alexey Zinger <[email protected]>
Sent: Mon, March 14, 2011 9:04:31 AM
Subject: Re: [The Java Posse] Some design questions (about immutability and
other stuff)
On 14 March 2011 10:40, Fabrizio Giudici <[email protected]> wrote:
On 03/14/2011 11:04 AM, Kevin Wright wrote:
>
>What a tangled web we weave...
>>
>>
OK, Kevin. Bytecode manipulation apart (you correctly depicted the scenario as
"vandalism" in this case and can't be avoided), does Scala add something
interesting to the topic? :-) I'm not pulling your legs, I'm really interested
to know whether other languages deal with it in a better way, or if in the end
it's only a matter of design.
>
>
There's a few bits that Scala helps with. The deep support for immutability
makes it far easier to get this code right (you don't have to worry about
missing a `final`), the collection library is a much better fit, type inference
and implicit conversions also makes some design patterns a lot more practical,
and pattern matching is a natural fit for working with immutable objects. The
syntactic-sugar/eye candy also helps make the intent of the code far clearer.
First class functions/closures are very relevant too
Getting really deep, there's also a lot that can be done with type classes and
monadic comprehension to make the code cleaner and more generic, but it's far
from essential and I don't want to scare anyone off :)
File handling is such a rich topic that it's hard to know what will interest
people, so I'm just going to throw out a brain dump with some random stuff...
m'kay?
As always, it's easier to show with some examples:
val somedir = FileRef of "C:\somedirectory"
In this case, I'm invoking the `of` factory method on the `FileRef` singleton.
This method accepts a URI argument, so you're also using an implicit
conversion
from String=>Uri. De-sugaring, it's equivalent to:
val dir: FileRef = FileRef.of(Uri("C:\somedirectory"))
So how about those refs? Assuming here that we have a hierarchy something like:
FileRef
- DirectoryFileRef
- StreamableFileRef
- AppendableFileRef
- RandomAccessFileRef
- NonExistantFileRef
That FileRef isn't much use as it stands, the concrete instance could be any of
those subclasses, so to print a directory list you might use pattern matching:
dir match {
case x: DirectoryFileRef => println(x.contents mkString "\n")
case _ => error(dir.toString + " is not a directory)
}
Building on the API a bit more, you could also have an asDirectory method on
FileRef, which would return an `Option[DirectoryRef]`. Allowing that example to
be rewritten:
dir.asDirectory foreach { d => println(d.contents mkString "\n") }
(think of Option as a collection with 0 or 1 elements, where we're supplying a
closure to be used for each element in that collection)
Combined with `containedFiles` and `subdirectories` as methods on
DirectoryFileRef, you can write code like this to print the name of the first
file in the first subdirectory:
val dir = (FileRef of "C:\somedirectory").asDirectory
val subDirs = dir flatMap { _.subdirectories }
val firstSubDir = subDirs.headOption
val subdirEntries = firstSubDir flatMap {_.containedFiles}
val firstSubEntry = subdirEntries.headOption
firstSubEntry foreach { println("first subentry is " + _) }
`headOption` will return the first element of a collection as an Option (None
if
it's called on an empty collection). So no risk of NPEs here!
It's guaranteed not to throw an exception (notwithstanding some catastrophic
problem in the filesystem), but still a bit verbose.
You can also do the same thing more cleanly with a for-comprehension.
Including
filtering and returning a collection for further processing:
val tmpFiles = for {
dir <- fileRef.asDirectory
subdir <- dir.subdirectories filter (_.name startsWith "temp")
files <- subdir.containedFiles filter (_.extension == "tmp")
file <- files
} yield file
Much cleaner :)
To work with file contents, you can then use closures, following a pattern
similar to Jdbc Templates in Spring.
Given an update method that accepts a RandomAccessFileRef and a closure, you
can
then write (glossing over the need to handle string and byte-based files
differently):
val fileRef: RandomAccessFileRef = ...
File.update(fileRef) { contents => contents ++ "New last line\n" }
or
File.update(fileRef){ someLongComplicatedFunctionTakingAContentsArgument }
This would handle resource management, file opening/closing, etc. for you.
Could probably think of a lot more, but I've already rambled far too much as it
is.
--
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.