【问题标题】:Understanding Mixed Context Bounds of Seq[AnyVal] and Seq[String]了解 Seq[AnyVal] 和 Seq[String] 的混合上下文边界
【发布时间】:2020-10-03 11:21:37
【问题描述】:

假设我有一些函数应该采用 Ints 序列或 Strings 序列。

我的尝试:

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

但这行不通。

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

【问题讨论】:

  • 如果它们是 2 种不同的类型,那么您可能应该为它们编写 2 个不同的函数。除了加号运算符之外,我看不到任何 IntString 共享的属性,这对它们来说并不完全相同
  • 你想要一个typeclass
  • 谢谢 - 这个函数本质上只是将这两个序列通过管道传输到一个 API 中。 API 只能接受 AnyVal 或 String。用户只需将数组中的数据传递给相同的函数端点。
  • 谢谢@LuisMiguelMejíaSuárez,这看起来很有趣,会读一读!不知道这个概念。
  • 谢谢@MarioGalic,我不知道 - 我会确保以后不会在问题中添加标签。

标签: scala generics types type-conversion context-bound


【解决方案1】:

注意上限

之间的区别
A <: C

还有一个context bound

A : C

所以类型参数子句[T &lt;: 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

【讨论】:

    【解决方案2】:

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

    在 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 个函数以及一个 trait,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

    【讨论】:

    • 啊,是的,但在这种情况下,只需使用 Either[Int, String],这是 Scala 表示 sum 类型的方式
    • @JoostPapendorp 我想这可能是一种更实用的方法,但无论如何:)
    • 感谢第二个看起来很优雅
    • "a sequence of Ints or a sequence of Strings"是Seq[Int] | Seq[String],不是Seq[Int | String];第二个允许例如Seq(0, "").
    【解决方案3】:
    def f1( seq: Seq[Any] ): Unit = 
      println( seq( 0 ) )
    

    这是可行的,因为 Int 和 String 的最不常见的超类型(两者的超类型的“最低”类型)将是 Any,因为 Int 是 AnyVal 的子类型(即“原语”)和字符串是AnyRef 的子类型(即“堆对象”)。 f1 不依赖于列表的内容,其返回类型 (Unit) 也不是多态的,因此我们根本不需要类型参数。

    【讨论】:

      猜你喜欢
      • 2017-12-29
      • 1970-01-01
      • 2022-08-23
      • 1970-01-01
      • 1970-01-01
      • 2020-08-12
      • 1970-01-01
      • 2022-07-16
      • 2018-06-29
      相关资源
      最近更新 更多