mwlon
mwlon

Reputation: 1006

How to enforce a context bound on a wildcard in Scala?

I have an implicit helper set up like this:

trait Helper[T] {
  def help(entry: T): Unit
}

object Helpers {
  implicit object XHelper extends Helper[X] {
    override def help(entry: X): Unit = {println("x")}
  }
  implicit object YHelper extends Helper[Y] {
    override def help(entry: Y): Unit = {println("y")}
  }

  def help[T](entry: T)(implicit helper: Helper[T]): Unit = {
    helper.help(entry)
  }
}

I would like to set up a collection of elements and run help on each of them. However, the following gives a compiler error because we can't guarantee all elements have matching Helpers:

val data = Seq[_](new X(), new Y())
data.foreach(entry => Helpers.help(entry))

If we had a generic type T we could enforce the implicit constraint on it with [T: Helper], but that doesn't work on _. How can I enforce that each element of data has a matching Helper?

Upvotes: 1

Views: 289

Answers (2)

Kolmar
Kolmar

Reputation: 14224

In Scala context bound like class A[T: Typeclass] is just syntactic sugar for class A[T](implicit ev: Typeclass[T]). Unlike T <: Base or T >: Super, context bound is not really a part of a type signature, so you can't have a signature like val b: Box[T: Typeclass].

If you want to run typeclass operations on elements of some container, you'd have to pack relevant typeclass instances together with the values in the container.

A possible implementation of this may look as follows:

import language.higherKinds
import language.implicitConversions

// Class that packs values with typeclass instances
class WithTC[T, TC[_]](t: T)(implicit tc: TC[T]) {
  // Some helper methods to simplify executing typeclass operations
  // You may just make `t` and `tc` public, if you wish.
  def apply[U](op: (TC[T], T) => U) = op(tc, t)
  def apply[U](op: T => TC[T] => U) = op(t)(tc)
}
object WithTC {
  // Implicit conversion to automatically wrap values into `WithTC`
  implicit def apply[T, TC[_]](t: T)(implicit tc: TC[T]): WithTC[T, TC] =
    new WithTC(t)(tc)
}

Then you can make a sequence with existentially typed elements:

import Helpers._
val data: Seq[(T WithTC Helper) forSome { type T }] = Seq(new X(), new Y())

And execute typeclass operations on the sequence elements:

// The following lines produce equivalent results
data.foreach(_(_ help _))
data.foreach(_(t => implicit tc => Helpers.help(t)))
data.foreach(_(t => Helpers.help(t)(_)))

Upvotes: 1

adamwy
adamwy

Reputation: 1239

It's not possible with type like Seq since it is only parametrized for one element type that is common for all its elements.

However, you can achieve this with Shapeless HLists and polymorphics functions (Poly):

class X
class Y

trait Helper[T] {
  def help(entry: T): Unit
}

object Helpers {
  implicit object XHelper extends Helper[X] {
    override def help(entry: X): Unit = println("x")
  }

  implicit object YHelper extends Helper[Y] {
    override def help(entry: Y): Unit = println("y")
  }
}

import shapeless._

object helper extends Poly1 {
  implicit def tCase[T: Helper]: Case.Aux[T, Unit] = 
    at(implicitly[Helper[T]].help(_))
}

val hlist = new X :: new Y :: HNil
hlist.map(helper)

// Output:
x
y

Upvotes: 0

Related Questions