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

拟墨画扇 提交于 2020-06-26 14:12:21

问题


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?


回答1:


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



回答2:


I feel like you should write a separate function for both, but there are other ways to do it:

In Dotty, you can just use union types, which is what I'd recommend here:

Edit: As per Alexey Romanov’s suggestion, replaced Seq[AnyVal | String] with Seq[AnyVal | String], which was wrong,

def foo(seq: Seq[AnyVal] | Seq[String]): Unit = {
  println(seq)
}

Scastie


If you're not using Dotty, you can still do this in Scala 2 with a couple reusable implicit defs


class Or[A, B]

implicit def orA[A, B](implicit ev: A): Or[A, B] = new Or
implicit def orB[A, B](implicit ev: B): Or[A, B] = new Or

def foo[T](seq: Seq[T])(implicit ev: Or[T <:< AnyVal, T =:= String]): Unit = 
  println(seq)

foo(Seq("baz", "waldo"))
foo(Seq(23, 34))
foo(Seq(List(), List())) //This last one fails

Scastie


You can also do it with a typeclass, as Luis Miguel Mejía Suárez suggested, but I wouldn't recommend it for such a trivial task since you still have to define 2 functions for each type, along with a trait, 2 implicit objects, and one function that can use instances of that trait. You probably don't want this pattern to handle just 2 different types.

sealed trait DoSomethingToIntOrString[T] {
  def doSomething(t: Seq[T]): Unit
}

implicit object DoSomethingToAnyVal extends DoSomethingToAnyValOrString[AnyVal] {
  def doSomething(is: Seq[AnyVal]): Unit = println(is)
}

implicit object DoSomethingToString extends DoSomethingToIntOrString[String] {
  def doSomething(ss: Seq[String]): Unit = println(ss)
}

def foo[T](t: Seq[T])(implicit dsis: DoSomethingToIntOrString[T]): Unit = {
  dsis.doSomething(t)
}

foo(Seq("foo", "bar"))
foo(Seq(1, 2))

In Scastie




回答3:


def f1( seq: Seq[Any] ): Unit = 
  println( seq( 0 ) )

This works, because the least common supertype of Int and String (the 'lowest' type that is a supertype of both) would be Any, since Int is a subtype of AnyVal (i.e. 'the primitives) and String is a subtype of AnyRef (i.e. `the heap objects). f1 does not depends in any way on the contents of the list and is not polymorphic in its return type (Unit), so we don't need a type parameter at all.



来源:https://stackoverflow.com/questions/62364645/understanding-mixed-context-bounds-of-seqanyval-and-seqstring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!