Recently, Mark Needham asked an interresting question: in what cases Scala structural types can be legitimately combined with self-type annotations.
Let me digress here to present the context first.
This all has begun with Mark's post that presents a code snippet combining the two features. Unlike some people I wasn't really excited about the example, quite opposite to be honest.
I expressed my criticism at the post comment, but I'll repeat the points here:
1. Structural types are optimal to uniformly handle types, that share some members, yet have no common ancestor. Like Socket and ServerSocket in java.net - both have close(), isConnected(), isBound(), getInetAddress() methods, yet you cannot treat them uniformly in Java. Moreover you cannot change those classes, because they are from the standard library. In the Mark's example all classes were under his control, so he could make them implement a common trait easily.
2. Self-type annotations shine when you have a coherent set of abstract members you want to have available for your trait's methods. Just wrap the related members into a separate trait and declare it as your self type. The first benefit you get is that the syntax clearly communicates the connection between the abstract members (they are grouped in one type). The other, your traits are now connected at one level higher, without the need for enumerating all necessary abstract members in your main trait.
There is little sense in using structural types for classes that you control. Often there is also little sense in extracting the only abstract member into its own type (named or structural, regardless) just to declare it as a self-type. I don't mean always, because in some cases you want it to communicate, that the abstract member is a (possibly only, but still) part of some important, independent concept.
In Mark's example, I just couldn't tell what concept is the "val peopleNodes: NodeSeq" member a part of. For that reason I found introduction of self-type there somewhat artificial.
Then there comes a question: is there a valid use case for combining structural types with self-type annotations. That was hard question for me, because I've never imagined such combination, nor met one. Nevertheless I've tried to imagine a case, where it could make sense.
So first, we should have at least two classes without a common ancestor, but structurally similar in order to meaningfully apply the Scala's structural types. Socket and ServerSocket from java.net came to my mind.
Second, we need to have trait with a bunch of abstract members and we should be able to divide the members into separate coherent groups in order for self-type annotation to make sense. I've invented a totally artificial "LoggableSocket" trait, that represents a socket-like object with an ability to log its status in some form. Without structural types and self-type annotations it (and its use) would look like:
import java.net._
trait LoggableSocket {
def isConnected(): Boolean
def isBound(): Boolean
def log(message: String): Unit
def logStatus() = {
log("Socket status: connected [" + isConnected() +
"], bound [" + isBound() + "]")
}
}
// helper trait
trait SysoutLogging {
def log(s: String) = println(s)
}
// usage
val s = new Socket() with LoggableSocket with SysoutLogging
s.logStatus()
Of course at least one thing is not that nice here: the abstract members related to Sockets are intermixed with the member related to logging. Let's fix it by extracting them to their own type and then declare it as the LoggableSocket's self-type:
trait SocketLike {
def isConnected(): Boolean
def isBound(): Boolean
}
trait LoggableSocket {
self: SocketLike =>
def log(message: String): Unit
def logStatus() = {
log("Socket status: connected [" + isConnected() +
"], bound [" + isBound() + "]")
}
}
// ...
val s = new Socket() with LoggableSocket with SysoutLogging // Ups! Does not compile
What has just happened? It doesn't compile, because Socket doesn't implement the SocketLike trait, which we've just declared as self-type dependency of LoggableSocket. Socket is a JDK class, so we cannot make it implement that interface.
We could either instantiate it adding another "with" after the constructor:
val s = new Socket() with SocketLike with LoggableSocket with SysoutLogging
(but the number of "with"s quickly gets out of control) or, we could - yes - introduce a structural type here:
trait LoggableSocket {
self: {
def isConnected(): Boolean
def isBound(): Boolean
} =>
def log(message: String): Unit
def logStatus() = {
log("Socket status: connected [" + isConnected() +
"], bound [" + isBound() + "]")
}
}
// ...
val s = new Socket() with LoggableSocket with SysoutLogging
...and everything works like a charm again. This time the related members are grouped into a meaningful way. That clearly communicates, that they are of a different nature from the third abstract member, the log method. In addition this works for impossible-to-modify JDK classes.
So as you can see - yes, combining the two features: self-type annotations and structured types occasionally makes sense.
Though I would be far from calling that example natural or a real-world one :)
I really doubt you would find many cases, when the combination makes sense. But maybe I just cannot come up with an example? Would be very interrested if you found a valid real-world one.
środa, 29 czerwca 2011
sobota, 11 czerwca 2011
SBT Installer Ubuntizer
Installation of Java-based development tools was often a pain in the neck for me and something ridiculously poor, compared to brilliant installation of Ubuntu packages. Luckily now we got Java, Tomcat, Maven at least packaged properly - kudos to package maintainers! I hate this whole "unpack tarball, set XXX_HOME, then update PATH" theme with passion. I want to code instead of fighting environment!
In the Scala land, those things are yet to be improved. You got cross-platform installer for Scala, but the excellent SBT still requires you to follow some manual steps.
Because I don't see much activity in this area, I've decided to stop complaining and instead to do something about it. At least one small step, to begin with.
Today I've packed the SBT installer into a valid sbt-0.7.7.deb package (hosted at my Dropbox account). This was my first Debian package, so I made it very minimal - just to have it installable and uninstallable (at least on Ubuntu, because Debian may require copyrights, policies - I don't really know).
EDIT: Now, SBT 0.10.0 as a Ubuntu package (also from my Dropbox) is available too.
Creating the package as a one-off task would not be very compelling for a person like me, because I'd hate repeating manual work. Therefore I've created a script, that first lookups the latest jar at the Google Code page, then downloads the jar and then packs it into a deb archive. I've written it so it can adapt to minor version changes, but of course I doubt it'll be that future-proof. Regardless, the foundation is now laid, and some minor tweaks will hopefully suffice when new versions of SBT are released.
I've pushed the script and helper files to the Github, so feel free to fork and to improve!
In the Scala land, those things are yet to be improved. You got cross-platform installer for Scala, but the excellent SBT still requires you to follow some manual steps.
Because I don't see much activity in this area, I've decided to stop complaining and instead to do something about it. At least one small step, to begin with.
Today I've packed the SBT installer into a valid sbt-0.7.7.deb package (hosted at my Dropbox account). This was my first Debian package, so I made it very minimal - just to have it installable and uninstallable (at least on Ubuntu, because Debian may require copyrights, policies - I don't really know).
EDIT: Now, SBT 0.10.0 as a Ubuntu package (also from my Dropbox) is available too.
Creating the package as a one-off task would not be very compelling for a person like me, because I'd hate repeating manual work. Therefore I've created a script, that first lookups the latest jar at the Google Code page, then downloads the jar and then packs it into a deb archive. I've written it so it can adapt to minor version changes, but of course I doubt it'll be that future-proof. Regardless, the foundation is now laid, and some minor tweaks will hopefully suffice when new versions of SBT are released.
I've pushed the script and helper files to the Github, so feel free to fork and to improve!
Subskrybuj:
Posty (Atom)