15

Suppose I'm using the typeclass pattern in Scala. Here's how I make a class C part of the typeclass Foo:

Welcome to Scala version 2.9.0.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_26).

scala> trait Foo[T] { def foo(t: T) }
defined trait Foo

scala> def foo[T : Foo](t: T) { implicitly[Foo[T]].foo(t) }
foo: [T](t: T)(implicit evidence$1: Foo[T])Unit

scala> class C
defined class C

scala> foo(new C)
<console>:11: error: could not find implicit value for evidence parameter of type Foo[C]
       foo(new C)
          ^

scala> implicit object FooC extends Foo[C] { override def foo(c: C) { println("it's a C!") } }
defined module FooC

scala> foo(new C)
it's a C!

So far so good. But suppose I have a subclass D of C, and I want instances of D to be "in" the typeclass too:

scala> class D extends C
defined class D

scala> foo(new D)
<console>:13: error: could not find implicit value for evidence parameter of type Foo[D]
       foo(new D)
          ^

Doh! How do I make this work without having to explicitly provide a typeclass instance for D?

Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
  • This is pretty much a duplicate of http://stackoverflow.com/questions/3869991/type-class-pattern-in-scala-doesnt-consider-inheritance – Seth Tisue Sep 20 '11 at 15:30

1 Answers1

16

There are different possible solutions for this, depending on whether I want to fix the problem only for C, or whether I want to fix the problem for the entire typeclass.

For C only, instead of implicit object FooC ... we say:

implicit def CIsFoo[T <: C]: Foo[T] =
  new Foo[T] { override def foo(t: T) { println("it's a C!") } }

To fix all of Foo, make it contravariant:

trait Foo[-T] { def foo(t: T) }

Or if for some reason you can't or don't want to do that, you can replace def foo... with:

def foo[T](t: T)(implicit foo: Foo[_ >: T]) =
  foo.foo(t)

(Thanks to #scala denizens Daniel Sobral and Stefan Zeiger for their help.)

UPDATED Sep 20 2011 to include the "make Foo contravariant" solution, which I missed

Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
  • 1
    There's another question lurking here... What if I want D to be treated slightly differently than T, *but* share some common code! – jsuereth Jul 13 '11 at 17:53
  • 3
    @jsuereth Nothing prevents you from declaring an `implicit object FooD` for `D` as you did for `C`, and call methods from `FooC` in `FooD`. – Jean-Philippe Pellet Jul 13 '11 at 18:30
  • 1
    Actually you should try it. 'Conflicting implicit imports' unless you muck with priorities. The full solution looks pretty boiler-platey and can be frustrating to muck with. – jsuereth Jul 14 '11 at 15:06