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)
}
@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