This list is my no means complete and as with the other list, I'll be appending to it here as I learn more.
Pimp My Library
I'll dive into some general theory about this first, but for those who just want to see the code, just scroll down.
There's been so many times when I've needed to add methods to classes, for good reasons. Well, I suppose I didn't "need" to, but the main good reason of course is readability. Having your data and operations on that data in one place is nice. Calling those operations consistently is very important.
Let's use an example in Scala. I couldn't find a "compact" method on List. It like seems it should be there. It's certainly useful. I could just create a method called compact, which takes a List, like so:
def compact[T]( list: List[T] ) = l.filter( _ != null )
But as I just said, calling operations on your data consistently is important for readability. Having this method forces me to call my operations differently. It's confusing calling some methods as infix, and some as prefix. Sometimes I say myList.whatever, and other times I say whatever(myList). The situation is even worse if that method takes arguments. myList.whatever(x), or whatever(myList, x). Inconsistent, confusing, ugly.
One solution is to simply add that method to the class you're operating on. Scala and Ruby both provide mechanisms for doing so, but they differ greatly. In Scala, I have to provide an implicit conversion from the class I'm operating on to a class that has a the method that I want to call. Here is the code:
class Compactable[T](l: List[T]){
def compact = l.filter( _ != null )
}
implicit def compactableList[T]( l: List[T] ) = new Compactable(l)
val list = List( 1, null, 2, null, 3, null )
println(list) // prints all values including the nulls
println(l.compact) // prints just the values 1, 2, and 3
This isn't terrible, but it really clouds my actual intent: I just want to add a compact method to List. Why should I have to convert it to something else, only to have the compiler figure out what I really wanted? While there are some other great uses for implicit conversions, in this particular case something that should be very simple has been made overly complex. Maybe there is a better way of doing this in Scala but I haven't seen it. In addition, I've seen this pattern so many times in Scala code written by other people, so I don't think there is a different way. This code is really just boilerplate.
In Ruby things are much more straight forward.
module Enumerable
def collect_with_index
idx = -1
collect{|elm| idx = idx + 1; yield(idx, elm)}
end
end
(Note: I've used a different method because the compact method already exists. I couldn't find a collect_with_index method.)
Here, I essentially just open up Enumerable and add the method to it. Done. The intent is preserved. Everything is clear, and simple. So simple in fact, that I don't even think I need to explain it.
I could imagine something very similar existing in Scala.
extend trait List[T]{
def compact = filter( _ != null )
}
This doesn't violate encapsulation because I'm not accessing any internals of List that aren't available to me otherwise. This could just be syntactic sugar for the Scala code above. Maybe someday.
Also, I think C# has similar mechanisms, but I'm admitting my inexperience. I'd love to see replies on how this is done in other languages.
Classes are Objects
In Scala, and Java, classes aren't really objects, at least the way they should be. Ruby (and I'm sure Smalltalk, and other languages) gets this right.
I had a discussion with my friend the other day and he asked, "what do you mean? I can call getClass on an object, and call methods on that class...". Eh, you can, but its still a weird fabrication. To a Rubyist, its laughable. So, without further ado, here's an example for Java programmers (in Java because Scala doesn't have static methods)
Let's say I have a simple little interface for a factory that creates Connection objects, and an implementation.
interface ConnectionFactory{
Connection createConnection( String user, String pass );
}
class ConnectionFactoryImpl implements ConnectionFactory{
Connection createConnection( String user, String pass ){
return new WhateverConnection( user, pass );
}
}
Then I have a client that uses a ConnectionFactory:
class ConnectionFactoryClient{
ConnectionFactory factory;
ConnectionFactoryClient( ConnectionFactory factory ){
this.factory = factory;
}
void doIt(){
Connection c = factory.createConnection( "dood", "secret" );
c.connect();
}
}
Now, imagine I had a class that had createConnection as a static method like so:
class ConnectionFactoryClass{
static Connection createConnection( String user, String pass ){
return new OtherConnection( user, pass )
}
}
You can see that the class itself seems like an object that implements the ConnectionFactory interface. If classes were really first class objects, and the class objects themselves could implement interfaces, then I could pass this class into the client like so:
new ConnectionFactoryClient( ConnectionFactoryClass )
You can do this easily in Ruby. This is because classes in Ruby are objects that respond to messages. They aren't really much different than the instances they create, just that the instances respond to a different set of messages the class.
I'm not advocating static methods here. This is something different entirely. Because the class is an object, and can be swapped out for a different object, you're not really statically bound to using that class the same way you would be in Java.
If classes could implement interfaces, you wouldn't have to go through the pain of creating a Factory instance, and passing that in, like this:
ConnectionFactory cf = new ConnectionFactoryImpl()
new ConnectionFactoryClient( cf )
Why go through the pain of creating a new class, and a new instance of a class, when you could use the class object itself. For the most part, classes are factories anyway.
Consider the following common pattern:
class MySQLConnection{
static public void createConnection( String user, String pass ){
new MySQLConnection( user, pass );
}
private MySQLConnection( String user, String pass ){ ... }
}
All the create logic is in one place, and the class is the factory. This, I think, is a good thing.
How could this be done you ask? To make this work with static typing, the language would have to have some construct that allows class objects to implement interfaces. It could look something like so (purpose any syntax you wish):
class MySQLConnection
(class implements ConnectionFactory)
(instances implement Connection){
static public void createConnection( String user, String pass ){
new MySQLConnection( user, pass );
}
private MySQLConnection( String user, String pass ){ ... }
}
We could probably take this even further to allow the constructors to serve as the implementations of the interface methods (maybe by giving constructors aliases).
One final note, in Scala, this would eliminate the need for the top level object that gets created when you define a case class. The class itself could be the object, and it would contain the apply method.
Anyway, that got long. I need to do something to structure this list a bit better.
Scala's library doesn't have compact because we're allergic to nulls :-)
ReplyDeleteIf you don't plan on using Compactible in other places, the code can be shrunk a bit with
implicit def compactableList[T]( l: List[T] ) = new {
def compact = l.filter( _ != null )
}
C# does indeed have something along the same lines called extension methods. F# has something as well, but I can't remember what they call it.
Ahh, thanks a lot James. That is nicer. It's still a somewhat roundabout way to say "I want List to have a compact method", but definitely cleaner because having to create a new class just for that purpose seemed wrong.
ReplyDeleteAnd, I agree with being allergic to nulls, but the actual method I wanted to implement had nothing to do with this post. But, keeping on the nulls issue anyway, maybe you can help me.
Here is a very specific example that I came across at work. We get a feed of Author data, containing first, middle and last name. (I'm writing in Ruby at work, but I'll speak in Scala).
I could have:
case class Name( f: String, m: String, l: String )
My strategy for pretty printing the name was:
List(f, m, l).compact.join(" ")
That seemed really nice, easy, elegant.
I can think of other ways to represent this, such as using Options:
case class Name( f: Option[String], m: Option[String], l: Option[String] ){
def pretty : String = {
List(f, m, l).compact.map( o => o.get ).mkString(" ")
}
}
implicit def compactableList[T]( l: List[T] ) = new {
def compact = l.filter( _ match { case Some(x) => true; case _ => false } )
}
println("name: " + Name(Some("Josh"), None, Some("Cough")).pretty)
But, I think this is a heck of a lot uglier and more confusing, both in the library, and in the client code. I am tired and not thinking well though, so maybe you have a better solution?
Oh, and I guess I left out the important fact that f, m, or l might not be present in the feed.
ReplyDeleteGiven your domain, is there a real semantic difference between "" (the empty string) and null (or None)? In other words, can you map a missing element to "" when it comes from the feed?
ReplyDeleteOtherwise I don't really have a significantly better answer.
No difference. So, I guess List(f,m,l).mkString(" ") would do just fine.
ReplyDeleteThanks.
I just spent pretty much the entire day working on pimping in Scala. I'm still convinced I'm right. But, overall, its really not that big of a deal. Here's some code I wrote:
ReplyDeleteobject PimpedProcess {
implicit def stringToRunningProcess(command: String): PimpedProcess = Runtime.getRuntime.exec(command)
implicit def pimpMyProcess(p: Process) = new PimpedProcess(p)
}
class PimpedProcess(p: Process) {
val br = new BufferedReader(new InputStreamReader(p.getInputStream))
var str = br.readLine
def hasNext = str != null
def next = {val current = str; str = br.readLine; current}
def watch(grep: Array[Char]) = while (hasNext) {println(next)}
}
Not only are there the problems I mentioned previously, but also, you have to reference the object being pimped, in the pimping code. For example: p.getInputStream. In Ruby you could just call getInputStream.
Pretty trivial stuff overall, but its these bunch of little things that add up to a more pleasant experience with Ruby, and I just don't think it has to be that way with Scala....
On implicit functions, there might be cases where "open" classes like those in Ruby have their benefits. However open classes leads to global namespace pollution and as a consequence monkey patching.
ReplyDeleteThere are alternative to open classes available on the JVM like
http://pleiad.dcc.uchile.cl/research/software/classboxes
In summary there are better alternatives to Ruby's version of open classes.
On the second example "Classes are Object", I do not understand what you are trying to accomplish. Here is why
* Classes are not Factories - tho they are occasionally used as such, and if poorly coded (IMHO) more than just occasionally.
Classes (Factories) as you describe them are best implemented as Singleton Objects, which is what scala provides.
Classes are a mechanism for abstraction, a means for specialization of behaviour.
In your example what happens when I extends MySQLConnection as such
class HisSQLConnection extends MySQLConnection
(I used His, as I was not certain if you mean mysql db connection)
Does HisSQLConnection derive its implementation from of the "class interfaces" from MySQLConnection, what if I do not wish to implement the factory abstractions and would just like to implement/extend Connection semantics.
In this case I would certainly lean towards an companion-object/singleton implementation for factory methods.
Hi Bhaskar,
ReplyDeleteIn the first case, I was really just advocating a more terse syntax for implicits when the intent is just to add a method to a class. I understand the problems involved with open classes, but in my (albeit very brief) Ruby experience, I haven't really run into those problems. I suspect they are rare. Also, thanks for the link. I'll check it out soon.
On the second point, I totally agree the Classes as Objects thing was a stretch, and your points are all valid. I think in this case I may have taken my need to remove all redundant code a bit too far. :)
I would like to press you on the "classes are not factories" point you made though. Maybe I just need to be further educated on this, but, if the constructor is in the class, and you can ask the class for a new instance, well, isn't that a factory? How isn't a class a factory?
My idea here was, if the class serves as a factory, then actually being able to use it as a top level object would allow me to reduce my line of code count. Any time I have to create an object just to call a method on something else makes me feel funny.
Additionally, for no good reason, I'm always hoping to get to the "everything is an object" nirvana.
But like I said, I was mostly just stretching it.