scalagenericstypestype-conversioncontext-bound

Understanding Mixed Context Bounds of Seq[AnyVal] and Seq[String]


Suppose I have some function that should take a sequence of Ints or a sequence of Strings.

My attempt:

object Example extends App {
  import scala.util.Random
  val rand: Random.type = scala.util.Random

  // raw data
  val x = Seq(1, 2, 3, 4, 5).map(e => e + rand.nextDouble())
  val y = Seq("chc", "asas")

  def f1[T <: AnyVal](seq: Seq[T]) = {
    println(seq(0))
  }

  // this works fine as expected
  f1(x)
  // how can i combine
  f1(y)
}

How can I add this to also work with strings?

If I change the method signature to:

def f1[T <: AnyVal:String](seq: Seq[T])

But this won't work.

Is there a way to impose my required constraint on the types elegantly?


Solution

  • Note the difference between an upper bound

    A <: C
    

    and a context bound

    A : C
    

    so type parameter clause [T <: AnyVal : String] does not make much sense. Also types such as String are rarely (or never) used as context bounds.


    Here is a typeclass approach

    trait EitherStringOrAnyVal[T]
    
    object EitherStringOrAnyVal {
      implicit val str: EitherStringOrAnyVal[String] = new EitherStringOrAnyVal[String] {}
      implicit def aval[T <: AnyVal]: EitherStringOrAnyVal[T] = new EitherStringOrAnyVal[T] {}
    }
    
    def f1[T: EitherStringOrAnyVal](seq: Seq[T]): Unit = {
      println(seq(0))
    }
    
    f1(Seq(1))       // ok
    f1(Seq("a"))     // ok
    f1(Seq(Seq(1)))  // nok
    

    or generelized type constraints approach

    object Foo {
      private def impl[T](seq: Seq[T]): Unit = {
        println(seq(0))
      }
      def f1[T](seq: Seq[T])(implicit ev: T =:= String): Unit = impl(seq)
      def f1[T <: AnyVal](seq: Seq[T]): Unit = impl(seq)
    }
    
    import Foo._
    
    f1(Seq(1))       // ok
    f1(Seq("a"))     // ok
    f1(Seq(Seq(1)))  // nok