Non default constructor type class

To be honest I am not sure if I am asking the right question. I suppose I may completely misunderstanding the point, but here is my question. I understand the basic way of creating type class as typical example like Show, which in my viewpoint it uses default constructor to create instance implicitly as below

trait Show[T] {
  def show(msg: T): String
}
object Show {
  def apply[T](implicit s: Show[T]) = s
  def show[A: Show](msg: A) = Show[A].show(msg)
  implicit val intShow = new Show[Int] {
    override def show(msg: Int) = s"msg: $msg"
  }
}
object MyExample {
  def main(args: Array[String]): Unit = {
    import Show._
    println(show(3))
  }
}

But now I have a problem. If I have a trait instance that may take some other inputs during instance creation. For instance, new Service(service1, service2) where service1 and service2 may be another input. How can I construct the implicit instance in such case. What I can think of is to define an implicit function that takes input(s) and use that function to instantiate the instance, but this seems to me implicit is useless (because I have to explicitly use it). Generally what would be recommended to deal with such case? Thanks

trait Service[I, O] {
    def run(): O
}
object Service {
    def apply[I, O](implicit s: Service[I, O]) = s
    implicit def service[I, O](in: I, f: I => O) = new Service[I, O] {
        def run(): O = f(in)
    }
}
object Another {
  def main(args: Array[String]): Unit = {
    import Service._
    val inc = (in: Int) => in + 1
    val s = service(3, inc)
    println(s.run())
  }
}

For starters, these don’t really look like type classes, but like implicits as depedency injection.
But to answer the question, an implicit def can have implicit parameters. So for you example where you have a service that is made up of other services you could have

trait Service1 { ... }
trait Service2 { ... }

trait AggregatedService {
  def service1: Service1
  def service2: Service2
}
object AggregatedService {
  implicit def mkAS(implicit s1: Service1, s2: Service2) = new AggregatedService {
    val service1 = s1
    val service2 = s2
  }
}

However for your Service[I, O] example it probably doesn’t make sense for I and I => O to be implicitly available. But again these are not really typeclasses. I guess you just want to use implicits as a form of dependency injection here. For that purpose it’s not necessary to implicitly create your Service and it probably wouldn’t make sense anyway (except perhaps for an AggregatedService, or to get a Service1 from an AggregatedService). You just need to have an implicit parameter of Service[I, O] on every class or method that requires access to it. And then you wire up the dependencies at the edge of your application.

object EntryPoint {
  def main(args: Array[String]): Unit = {
    implicit val service1 = new Service1
    implicit val service2 = new Service2
    Application.doMagic()
  }
}

object Application {
   def doMagic()(implicit s1: Service1, s2: Service2) = {
     hocus()
     pocus()
   }

   def hocus()(implicit s1: Service1) = {
     ...
   }
   def pocus()(implicit s2: Service2) = {
     ...
   }
}
2 Likes

If that’s not type class, then what’s missing and what should I change to make it a type class? I completely was not aware of it. Thank you for pointing this out.

type class has a completely different use case from dependency injection, so I’m not sure it makes sense to change your Service[I, O] into one.

A typeclass is an alternative to inheritance for polymorphic code.
For example, Show is a typeclass that you can use to write methods, that require something to be convertible to a string. Let’s say, you want to write a method for printing a list of any type that has a string representation explicitly defined for it (so no toString, which is also inherited from AnyRef with mostly useless result). With inheritance, you’d write something like this:

trait Showable {
  def show: String
}

def printList[A <: Showable](lines: List[A]) =
  lines.map(a => a.show).foreach(println)

So you require the type A in the list to extend the Showable trait.

With the Show typeclass, you would instead write (using the Show definition from your example):

def printList[A](lines: List[A])(implicit S: Show[A]) =
  lines.map(a => S.show(a)).foreach(println)

This means instead of making A a subtype of some trait, you pass in an instance of the typeclass for A (usually implicitly). So instead of having functionality added via inheritance, you add it via a separate object, matching the extended type. Because of that, typeclasses always have at least one type parameter.

The same happens in your example in the Show.show method. The [A: Show] is syntactic sugar for requiring an implicit parameter, called a context bound. The apply method in Show is just used as a shortcut for writing implicitly[Show[A]], as a context bound does not assign a name to the Show instance. So these are the same:

  def show[A: Show](msg: A) = Show[A].show(msg)
  def show[A](msg: A)(implicitly S: Show[A]) = S.show(msg)

This also means, that when calling show, the compiler will look up an instance of Show, that is available in implicit scope, based solely on the type. So you (usually) should only have one instance of a typeclass for a given type.

I’m not sure, what you’re referring to by “default constructor”. If you meant apply, it’s just a shortcut as explained above. The only place where a default constructor is called is in this snippet:

  implicit val intShow = new Show[Int] {
    override def show(msg: Int) = s"msg: $msg"
  }

Here you create a new anonymous subtype of Show[Int], calling it’s default constructor with new and storing that instance in intShow. But the creation doesn’t happen implicitly, just it’s result is made implicitly available.
I hope that makes it clearer, what a type class is, and what its use cases are: adding behaviour to a type, like with inheritance but defined on a separate object (which has some nice benefits like being able to add a Show instance for builtins like Int, while you wouldn’t be able to make Int inherit something).


So back to your question of how to make Service[I, O] a type class: are you sure that is what you want? It would mean, that the values of in and f are fixed for a given combination of types I and O. Perhaps explain what you want to achieve, instead of which way you want to achieve it, so we can give better recommendations.

2 Likes

I know that typeclass can support ad hoc polymorphism, but I suppose I may completely misunderstand it or its use cases.

So let’s say I have a trait Config[T]. I can make instances of Config[typesafe.Config] or Config[Hadoop.Configuration], and then use those instances to decide how to read configuration data from external sources. Or I can make instances of Library[cats.effect.IO] or Library[Monix.Task], and use those instances to decide how to run a task. Am I correct in this sense? I understand the examples I imagine might not be suitable in reality, but just try to understand typeclass and its use cases in a higher level abstraction.

Thank you for detail explanation. Appreciate it!

Yes, that sounds more like applications for typeclasses. The reason why your trait Service[I, O] example was not fitting for a typeclass, was because it was abstacting over values (the function inc and the 3 passed to the service method). A typeclass is used to abstract over types, like the examples in your last post.

So in those, you’d write code that only refers to the Config or Library typeclass and only uses methods defined on it. You’d still pass in the concrete type e.g. typesafe.Config, but the parameter would be of a variable type. For example:

def useConfig[A](configObject: A)(implicit config: Config[A]) =
    config(configObject).someMethodDefinedInTypeclass(...)

The only thing your method knows about A, that there is a Config instance for it, but not which concrete type it is. This allows you to change which concrete class you use further up in the call hierarchy without needing to change anything here. You still have to pass in a concrete implementation at some point, which, for something like the configs or IO monads, is probably not too far from the entry point of your program, or at the topmost method using it.

Your second example (Library) is not only “suitable in reality”, such typeclasses are actually included in cats-effect and implementations for it included in monix, for example the Sync typeclass. As the types, for which you have a Sync instance, have a type parameter themselves, it gets a bit more complicated, because you’ll need a higher kinded type, but the principle is the same. A concrete example of its usage would be:

def printIO[F[_] : Sync](msg: String): F[Unit] =
  Sync[F].delay(println(msg))

// set a concrete type while calling
printIO[cats.effect.IO]("hello") //returns a cats.effect.IO[Unit]

// or just leave it for a higher up method to set it
def printIOIndirect[F[_] : Sync](msg: String): F[Unit] = printIO(msg)

Here we can again see the apply method of the typeclass’s companion (sometimes called a summoner) used to get the Sync instance for F, to then call the delay method on that instance. We don’t instantiate a typeclass anywhere here, those instances are usually defined in the companion object of the typeclass or the subject (so e.g. on the Sync or IO or Task companion). This is not only convention, but also the place the compiler will look for instances without explicitly importing them.

2 Likes

That’s pretty clear explanation. Now I suppose I have more concrete idea what I misunderstand (I mix up several concepts together and was not aware of that). Thank you again for the example. Appreciate it!