tags:

views:

217

answers:

2

Having written a few scala tools, I'm trying to come to grips with the best way to arrange my code - particularly implicits. I have 2 goals:

  • Sometimes, I want to be able to import just the implicits I ask for.
  • Othertimes, I want to just import everything.

To avoid duplicating the implicits, I've come up with this structure (similar to the way scalaz is arranged):

case class StringW(s : String) {
  def contrived = s + "?"
}

trait StringWImplicits {
  implicit def To(s : String) = StringW(s)
  implicit def From(sw : StringW) = sw.s
}

object StringW extends StringWImplicits

// Elsewhere on Monkey Island

object World extends StringWImplicits with ListWImplicits with MoreImplicits

This allows me to just

import StringW._ // Selective import

or (in most cases)

import World._. // Import everything

How does everyone else do it?

+3  A: 

I think that implicit conversions are dangerous if you don't know where they are coming from. In my case, I put my implicits in a Conversions class and import it as close to the use as possible

def someMethod(d: Date) ; Unit {
  import mydate.Conversions._
  val tz = TimeZone.getDefault 
  val timeOfDay = d.getTimeOfDay(tz) //implicit used here
  ...
}

I'm not sure I like "inheriting" implicits from various traits for the same reason it was considered bad Java practice to implement an interface so you could use its constants directly (static imports are preferred instead).

oxbow_lakes
Not that I necessarily disagree with this advice, but it might be pointed out that implicits are useful because they are, well, implicit. If you add an import statement just before use each time, there's perhaps an argument that you might as well have applied the implicit conversion explicitly.
Matt R
I was mulling over this exact point as I walked home yesterday (and I don't always follow it myself)! I still prefer imports over inheritance however.
oxbow_lakes
A: 

I usually had implicit conversions in an object which clearly signals that what it is imported is an implicit conversion.

For example, if I have a class com.foo.bar.FilthyRichString, the implicit conversions would go into com.foo.bar.implicit.FilthyRichStringImplicit. I know the names are a bit long, but that's why we have IDEs (and Scala IDE support is getting better). The way I do this is that I feel it is important that all the implicit conversions can be clearly viewed in a 10 second code review. I could look at the following code:


// other imports
import com.foo.bar.FilthyRichString

import com.foo.bar.util.Logger
import com.foo.bar.util.FileIO

import com.foo.bar.implicits.FilthyRichStringImplicit._
import com.foo.bar.implicits.MyListImplicit._
// other implicits

and at a glance see all the implicit conversions that are active in this source file. They would also be all gathered together, if you use the convention that imports are grouped by packages, with a new line between different packages.

Along the lines of the same argument, I wouldn't like a catch-all object that holds all of the implicit conversions. In a big project, would you really use all of the implicit conversions in all your source files? I think that doing that means very tight coupling between different parts of your code.

Also, a catch-all object is not very good for documentation. In the case of explicitly writing all the implicit conversions used in a file, one can just look at your import statements and straight away jump to the documentation of the implicit class. In the case of a catch-all object, one would have to look at that object (which in a big project might be huge) and then search for the implicit conversion they are after.

I agree with oxbow_lakes that having implicit conversion in traits is bad because of the temptation of inheriting from it, which is, as he said, bad practice. Along those lines, I would make the objects holding the implicit conversions final just to avoid the temptation altogether. His idea of importing them as close to the use as possible is very nice as well, if implicit conversions are just used sparingly in the code.

-- Flaviu Cipcigan

Flaviu Cipcigan