I first describe way of thinking, and then my questions. I use Functor definition as an example. It is defined in scalaz, so I use it as an example with small changes to make it less complex.
Here is a Functor
module definition, that I put in Functor.scala
file:
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
Now I want to define some implicits of the module for exact types, for instance for List
. Plus I want to define a generic fmap
function, that accepts Functor
implicitly. It is better to define them as close as possible to Functor
trait. I think the best place (consideration #1) in Scala for it is a companion object. Here is a definition of it in the same Functor.scala
file:
object Functor {
def fmap[F[_], A,B](as:F[A])(f:A=>B)
(implicit ff:Functor[F]):F[B] =
ff.map(as)(f)
implicit val listFunctor = new Functor[List] {
def map[A,B](as: List[A])(f: A => B): List[B] = as map f
}
}
I can use fmap
and Functor
implicitly in a client code, like:
import com.savdev.NewLibrary._
val r = fmap(List(1,2))(_.toString)
Quite often when we define a module with a function/set of functions we also want to enrich existing types, to be able to use our new functions as part of the API of these existing types. In Scala we can achieve it via implicit convertions. Here is a FunctorOps
final class, that allows to convert from some generic type to Functor
:
final class FunctorOps[F[_], A](self: F[A])(implicit ff:Functor[F]){
def qmap[B](f:A=>B):F[B] = ff.map(self)(f)
}
I name functions: fmap
, qmap
differently to avoid using by mistake a map
function that exists for different types.
Now I want to define an implicit convertor to FunctorOps
. As the first option that comes to my mind - to use a companion object. But looking at scalaz code I found, that they use mostly traits for it:
trait ToFunctorOps {
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
new FunctorOps[F,A](v)
}
Having traits I can compose them and import with one word. Here is an example of one trait, but I can extend a lot of them:
object NewLibrary extends ToFunctorOps
Right now in a client code I can import only NewLibrary
that is much easier than to import each companion object:
import com.savdev.NewLibrary._
val r = fmap(List(1,2))(_.toString)
Comparing options, traits vs. companion objects, I can find that using traits is much more flexible way. First of all cause I can compose them. Perhaps I miss something and my conclusion is wrong. So the questions are:
- Can you give me explicit use cases when using a companion object is more effective way for definitions of generic functions and implicits if such exist.
- Is there a way to compose companion objects? They are singletons, but Scala is more power language than I expected. Probably there are some ways to do that.
- Is there a guide that explains which of the options should I prefer and when.