3

I would like to define some behavior for those types that don't have an instance for a certain typeclass:

  // Given
  trait SomeTypeclass[T]

  // when we have implicit SomeTypeclass[T]
  def f[T: SomeTypeclass](x:T):Unit = ???
  // when we don't have instance
  def f[T !: SomeTypeclass](x: T):Unit = ???

We could handle the difference within the typeclass but then I would need to create extra instances just to support some generic behavior.

Is there a way to negate a type bound? A way to make the function with !: compile?

(I would like to do this in vanilla Scala, without scalaz, shapeless, etc)

muhuk
  • 15,777
  • 9
  • 59
  • 98
  • [Miles Sabin's type negation in practice](http://vpatryshev.blogspot.com/2012/03/miles-sabins-type-negation-in-practice.html) – muhuk Jun 01 '16 at 19:41

2 Answers2

5

Is there a way to negate a type bound?

No! The syntax [T: SomeTypeclass] is just shorthand for (implicit val t: Sometypeclass[T]), and there is no way to "negate" that. You could overload the method but that would create an ambiguity.

But you can "nest" type classes.

trait ExistsLowPri {
  implicit def no[A]: Exists[A] = Exists.No
}
object Exists extends ExistsLowPri {
  case class Yes[A](peer: A) extends Exists[A]
  case object No extends Exists[Nothing]

  implicit def yes[A](implicit peer: A): Exists[A] = new Yes(peer)
}
sealed trait Exists[+A]

Example:

trait Show[-A] {
  def show(x: A): String
}

def test[A](x: A)(implicit ex: Exists[Show[A]]): Unit = println(
  ex match {
    case Exists.Yes(s) => s.show(x)
    case Exists.No     => "(no string repr)"
  }
)

implicit object ShowBoolean extends Show[Boolean] {
  def show(b: Boolean) = if (b) "T" else "F" 
}

test(123)   // (no string repr)
test(true)  // T

However, I would strongly advise against doing this, because the main point of implicits and type classes is to have explicit compiler failure if something is not in scope. This way you will always be able to compile but have no guarantee that you correctly brought a specific type class into scope.

0__
  • 66,707
  • 21
  • 171
  • 266
5

You can roll your own:

trait NoInstance[T[_], A]

object NoInstance extends LowPriorityNoInstance {
  implicit def hasInstance0[T[_], A](implicit inst: T[A]): NoInstance[T, A] = ???
  implicit def hasInstance1[T[_], A](implicit inst: T[A]): NoInstance[T, A] = ???
}

class LowPriorityNoInstance {
  implicit def noInstance[T[_], A]: NoInstance[T, A] = new NoInstance[T, A] {}
}

And then:

scala> implicitly[NoInstance[Ordering, List[Int]]]
res4: NoInstance[Ordering,List[Int]] = LowPriorityNoInstance$$anon$1@5e1fc2aa

scala> implicitly[NoInstance[Ordering, Int]]
<console>:14: error: ambiguous implicit values:
 both method hasInstance0 in object NoInstance of type [T[_], A](implicit inst: T[A])NoInstance[T,A]
 and method hasInstance1 in object NoInstance of type [T[_], A](implicit inst: T[A])NoInstance[T,A]
 match expected type NoInstance[Ordering,Int]
       implicitly[NoInstance[Ordering, Int]]
                 ^

In many cases you can avoid this kind of thing with the null default implicit parameter trick, though:

def f[T](x: T)(implicit stc: SomeTypeclass[T] = null): Unit = Option(stc) match {
  case Some(instance) => // do something in the case where we have an instance
  case None => // do something in the case where we don't have an instance
}

This feels like a hack, but it works and people use it all the time.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680