Advanced Type Constraints in Scala - Logical Operators and Implicit Parameters

318 Views Asked by At

I have the following code which works well.

package com.andrew

object ExperimentWithTypeConstraints {

  def union[T](t: T)(implicit c: (T =:= String)) = t match {
    case s: String => println(s"Some nice string: $s")
  }

  def main(args: Array[String]): Unit = {
    union("Hello.")
  }
}

Output:

Some nice string: Hello.

I would like to modify the definition of the method union something like this:

   def union[T](t: T)(implicit c: (T =:= String) Or (T =:= Int)) = t match {
    case s: String => println(s"Some nice string: $s")
    case i: Int => println(s"Some int: $i")
  }

The output for union(1) should be the following one: Some int: 1

Unfortunately, Scala does not know the logical operator Or (And, Not, ..) for such cases and hence it is not possible to compile it. How can I do it?

I found one solution at the following address (http://hacking-scala.org/post/73854628325/advanced-type-constraints-with-type-classes) but unfortunately I don't understand it well. Can you tell me how to solve this problem based on your approach? Or can you explain the solution from the above-mentioned url? Their solution works and it is the following:

@implicitNotFound("Argument does not satisfy constraints: ${A} Or ${B}")
trait Or[A, B]

object Or {
  private val evidence: Or[Any, Any] = new Object with Or[Any, Any]

  implicit def aExistsEv[A, B](implicit a: A) =
    evidence.asInstanceOf[Or[A, B]]

  implicit def bExistsEv[A, B](implicit b: B) =
    evidence.asInstanceOf[Or[A, B]]
}

I don't understand the part with Or. Or is an object. How can I combine this part (T =:= String) Or (T =:= Int) via Or together?

Thanks a lot, Andrew

2

There are 2 best solutions below

8
Dmytro Mitin On

Lines

implicit def aExistsEv[A, B](implicit a: A) =
  evidence.asInstanceOf[Or[A, B]]

implicit def bExistsEv[A, B](implicit b: B) =
  evidence.asInstanceOf[Or[A, B]]

mean that if you implicitly have A then you implicitly have Or[A, B] and similarly if you implicitly have B then you implicitly have Or[A, B].

This is exactly how Or works in logic: if A is true then A Or B is true and similarly if B is true then A Or B is true.

And finally if T is String then implicit T =:= String is provided, and if T is Int then implicit T =:= Int is provided.

A X B is the same as X[A, B], so A Or B is the same as Or[A, B].

0
jwvh On

Unfortunately, Scala does not know the logical operator Or (And, Not, ..) for such cases and hence it is not possible to compile it. How can I do it?

Here's a different approach to the same problem. It is, admittedly, difficult to wrap your head around, but it works quite well.

trait Contra[-C]
type Union[A,B] = {
  type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]]
}

Usage:

def f[T: Union[String,Int]#Check](arg:T) = arg match {
  case s:String         => s"got string: ${s.toUpperCase}"
  case i:Int if i < 100 => s"got small Int: $i"
  case i:Int            => s"got bigger Int: $i"
}

Proof of concept:

f(119)            //res0: String = got bigger Int: 119
f("string time")  //res1: String = got string: STRING TIME
f(true)           //does not compile