Scala Typeclasses with generics
Asked Answered
A

1

9

I've been playing with the typeclass pattern in Scala, but I haven't been able to figure out how to implement the implicit companion object when the type I'm working with is generic.

For example, let's say I've defined a trait for a typeclass that provides functions for putting things into Boxes.

case class Box[A](value: A)

trait Boxer[A] {
  def box(instance: A): Box[A]
  def unbox(box: Box[A]): A
}

implicit object IntBoxer extends Boxer[Int] {
  def box(instance: Int) = Box(instance)
  def unbox(box: Box[Int]) = box.value
}

def box[A : Boxer](value: A) = implicitly[Boxer[A]].box(value)
def unbox[A : Boxer](box: Box[A]) = implicitly[Boxer[A]].unbox(box)

This works as expected, allowing me to provide implementations of Boxer for various types. However, I have no idea how I would do this when the type I wish to act on is generic itself. Let's say I wanted to be able to use my Boxer on any Seq[A]. objects in Scala cannot include type parameters, so I'm at a loss for where to go:

// Will not compile - object cannot have type arguments
implicit object SeqBoxer[A] extends Boxer[Seq[A]] { ... }

// Will not compile - 'A' is unrecognized
implicit object SeqBoxer extends Boxer[Seq[A]] { ... }

// Compiles but fails on execution, as this doesn't implement an implicit
// conversion for _specific_ instances of Seq
implicit object SeqBoxer extends Boxer[Seq[_]] {
  def box(instance: Seq[_]) = Box(instance)
  def unbox(box: Box[Seq[_]]) = box.value
}

// Will not compile - doesn't technically implement Boxer[Seq[_]]
implicit object SeqBoxer extends Boxer[Seq[_]] {
  def box[A](instance: Seq[A]) = Box(instance)
  def unbox[A](box: Box[Seq[A]]) = box.value
}

// Compiles, but won't resolve with 'implicitly[Boxer[Seq[Foo]]]'
// I had high hopes for this one, too :(
implicit def seqBoxer[A]() = new Boxer[Seq[A]] {
  def box(instance: Seq[A]) = Box(instance)
  def unbox(box: Box[Seq[A]]) = box.value
}

Is there any way to support implicit conversions of generic types without having to implicit a separate object for each internal type?

Abe answered 29/4, 2015 at 17:16 Comment(0)
R
8

You're really close, actually. You need to remove the parentheses from seqBoxer[A]. Otherwise, the compiler sees this as an implicit conversion from () => Boxer[Seq[A]], rather than simply an available implicit Boxer[Seq[A]]. For good measure, it is also a good idea to make the return type of an implicit method explicit.

implicit def seqBoxer[A]: Boxer[Seq[A]] = new Boxer[Seq[A]] {
  def box(instance: Seq[A]) = Box(instance)
  def unbox(box: Box[Seq[A]]) = box.value
}

scala> box(Seq(1, 2, 3))
res16: Box[Seq[Int]] = Box(List(1, 2, 3))

You can actually use this same approach to create a generic Boxer[A] for any A, should be required to behave the same way.

implicit def boxer[A]: Boxer[A] = new Boxer[A] {
    def box(instance: A): Box[A] = Box(instance)
    def unbox(box: Box[A]): A = box.value
}

scala> box("abc")
res19: Box[String] = Box(abc)

scala> box(List(1, 2, 3))
res20: Box[List[Int]] = Box(List(1, 2, 3))

scala> unbox(res20)
res22: List[Int] = List(1, 2, 3)

scala> box(false)
res23: Box[Boolean] = Box(false)
Reserve answered 29/4, 2015 at 17:24 Comment(1)
Ah! I'd totally forgotten about the difference between def foo: A and def foo(): A. This really clears things up!Abe

© 2022 - 2024 — McMap. All rights reserved.