Skip to content

Instantly share code, notes, and snippets.

@wfaler
Created July 4, 2012 17:55
Show Gist options
  • Save wfaler/3048592 to your computer and use it in GitHub Desktop.
Save wfaler/3048592 to your computer and use it in GitHub Desktop.
delegatingToTypeClassImplQuestion.scala
// Aim: Given a trait Client, I want to have one possible implementation that uses type classes to
//implement Clients abstract function, WITHOUT leaking through this fact to the Client traits API signature,
//as there are other, non- typeclass using impls.
trait Mapper[A]{
def map(s: String): A
}
object Mapper{
implicit object StringMapper extends Mapper[String]{
def map(s: String) = s
}
implicit object IntMapper extends Mapper[Int]{
def map(s: String) = s.toInt
}
def map[A](s: String)(implicit mapper: Mapper[A]): A = mapper.map(s)
}
trait Client{
def get[A: Manifest](s: String): A
}
// doesn't work, won't even compile
trait TypeClassUsingClient extends Client{
import Mapper._
def get[A: Manifest](s: String): A = map(s)
}
// compiles, but doesn't actually implement Client's abstract method
trait TypeClassUsingClient3 extends Client{
import Mapper._
def get[A: Manifest : Mapper](s: String): A = map(s)
}
@wfaler
Copy link
Author

wfaler commented Jul 4, 2012

It can blow up in a horrible stacktrace or whatever, it's the main issue of not being able to implement Client fully without polluting it with the implementation detail of TypeClassUsingClient which is my main problem (if you have an elegant solution for when a Mapper is not available, I don't mind though. :) )

@missingfaktor
Copy link

How is this one?

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Mapper[A]{
  def map(s: String): A
}

object Mapper{
  implicit object StringMapper extends Mapper[String]{
    def map(s: String) = s
  }
  implicit object IntMapper extends Mapper[Int]{
    def map(s: String) = s.toInt
  }

  def map[A](s: String)(implicit mapper: Mapper[A]): A = mapper.map(s)
}

trait Client{
 def get[A: Manifest](s: String): A
}

// Exiting paste mode, now interpreting.

defined trait Mapper
defined module Mapper
defined trait Client

scala> def optImplicitly[E](implicit ev: E = null) = Option(ev)
optImplicitly: [E](implicit ev: E)Option[E]

scala> trait TypeClassUsingClient extends Client {
     |   import Mapper._
     |
     |   def get[A : Manifest](s: String): A = optImplicitly[Mapper[A]] match {
     |     case Some(ev) => ev.map(s) // or `Mapper.map(s)(ev)`
     |     case None => sys.error("Not implemented yet.")
     |   }
     | }
defined trait TypeClassUsingClient

@wfaler
Copy link
Author

wfaler commented Jul 4, 2012

..seems to be close, but pushes the compilation problem to a RuntimeException:

  scala> val client = new TypeClassUsingClient{}
  client: java.lang.Object with TypeClassUsingClient = $anon$1@6487b71b

  scala> client.get[Int]("5")
  java.lang.RuntimeException: Not implemented yet.

@missingfaktor
Copy link

A subclass cannot add additional constraints on inherited methods. That would be a violation of Liskov substitution principle. (Or of contravariance.) That's why this cannot be a type error.

Suppose this was allowed, then consider:

var client: Client = new TypeClassUsingClient {}
client.get[Int]("5") // type of client implementation is unknown at compile time. 
                      // how can compiler know the additional 'compile time' constraints on method (those added by subclass)  then?
                      // ( I could assign client to something else. Something that doesn't have the same constraints. And that'll happen at runtime.)

@wfaler
Copy link
Author

wfaler commented Jul 4, 2012

Ah, so in other words what I tried to achieve is not solveable in Scala.

@missingfaktor
Copy link

No. Scala is not at fault. What you're considering is not logical. (I refer you to my previous example again.)

@wfaler
Copy link
Author

wfaler commented Jul 4, 2012

I understand why it is not possible, however I don't agree that what I considered "is not logical" unless you are aware of exactly how much the compiler can or cannot infer on it's own (an area where the Scala compiler is hideously inconsistent at times, sometimes being able to fluently infer definitions of monstrous complexity and sometimes stumbling on the simplest of syntax without additional type hints).

@missingfaktor
Copy link

I am pretty sure it's not logical. (Again, LSP.) Compiler cannot infer the things happening at runtime. I edited my code example to make explanation clearer.

Perhaps posting to mailing lists would be a good idea.

@wfaler
Copy link
Author

wfaler commented Jul 4, 2012

My original naive assumption was that the application can infer the type of A at runtime by means of Manifest, and that given Mapper._ is imported, it can infer what implementations it has available of Mapper[A] in scope, hence making a simple match between the two, if a match is possible.

I don't think that's an outrageous assumption given incomplete knowledge of the internals of exactly how it happens under the covers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment