理解 Seq[AnyVal] 和 Seq[String] 的混合上下文边界

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

假设我有一些函数需要一个整数序列或一个字符串序列。

我的尝试:

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)
}

我怎样才能添加它来处理字符串?

如果我将方法签名更改为:

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

但这行不通。

有没有办法优雅地对类型施加我需要的约束?

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

这是有效的,因为 Int 和 String 的最不常见的超类型('lowest' 类型是两者的超类型)将是 Any,因为 Int 是 [=12= 的子类型](即“原语”)和 String 是 AnyRef(即“堆对象”)的子类型。 f1 不以任何方式依赖于列表的内容,并且在其 return 类型 (Unit) 中不是多态的,因此我们根本不需要类型参数。

我觉得你应该为两者编写一个单独的函数,但还有其他方法可以做到:

在 Dotty 中,你可以只使用联合类型,这是我在这里推荐的:

编辑:根据 Alexey Romanov 的建议,将 Seq[AnyVal | String] 替换为 Seq[AnyVal | String],这是错误的,

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

Scastie


如果您不使用 Dotty,您仍然可以在 Scala 2 中使用一些可重复使用的 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


正如 Luis Miguel Mejía Suárez 所建议的那样,您也可以使用类型类来完成它,但我不建议将其用于这种微不足道的任务,因为您仍然必须为每种类型定义 2 个函数以及一个特征, 2 个隐式对象和一个可以使用该特征实例的函数。您可能不希望此模式只处理 2 种不同的类型。

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

注意 上限

之间的区别
A <: C

和一个context bound

A : C

所以类型参数子句[T <: AnyVal : String]没有多大意义。 String 等类型也很少(或从不)用作上下文边界。


这是一个类型类方法

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

或泛化类型约束方法

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