-
-
Save wfaler/3048592 to your computer and use it in GitHub Desktop.
// 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) | |
} |
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
..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.
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.)
Ah, so in other words what I tried to achieve is not solveable in Scala.
No. Scala is not at fault. What you're considering is not logical. (I refer you to my previous example again.)
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).
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.
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.
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. :) )