【问题标题】:Using same type parameter as argument type and parameter type with match expression使用与参数类型相同的类型参数和具有匹配表达式的参数类型
【发布时间】:2019-06-30 03:18:33
【问题描述】:

编译以下示例代码时出现错误。

abstract class Base
case class A(i: Int)    extends Base
case class B(s: String) extends Base

class Transform {
  def func[T <: Base](arg: T): T = arg match {
    case A(i) => A(i)
    case B(s) => B(s)
  }
}

错误是

Example.scala:9: error: type mismatch;
 found   : A
 required: T
    case A(i) => A(i)
                  ^
Example.scala:10: error: type mismatch;
 found   : B
 required: T
    case B(s) => B(s)
                  ^
two errors found

这些错误是合理的。
为了避免这种情况,我需要将asInstanceOf[T] 放在像A(i).asInstanceOf[T] 这样的实例化后面。但是,如果有很多匹配案例模式,那么对所有返回值都这样做很烦人。

另外,我想使用Transform类作为父类并覆盖func()来执行如下代码的具体操作。

class ExtTransform extends Transform {
  override def func[T <: Base](arg: T): T = arg match {
    case A(i) => A(i + 1)
    case _    => super.func(arg)
  }
}

有没有更好的方法或技巧?

【问题讨论】:

  • 谢谢!但是,您的建议不适合我。我不是很了解反射,但typeTag 似乎是用于类类型参数的。
  • 目标是什么? arg 已经是 AB,知道哪种类型又如何?
  • @cchantep 我的目标是返回 arg 的值类型值(不是变量类型值)。匹配表达式保证了这种操作的安全性(至少如果返回值是相同类型的匹配类型)。但是,scala 编译器无法检测到这一点。可能 scala 编译器现在会忽略使用匹配表达式来检查返回类型安全性的类型检查。
  • @cchantep 我想说的是,虽然我使用arg返回了由匹配表达式标识的类型,但scala编译器预测arg的类型和返回类型是不同的(例如arg的类型是A,返回类型是B)。

标签: scala generics type-parameter


【解决方案1】:

为了避免这种情况,我需要将 asInstanceOf[T] 放在像 A(i).asInstanceOf[T] 这样的实例化之后。但是,如果有很多匹配案例模式,那么对所有返回值都这样做很烦人。

嗯,这个问题很简单:把它放在比赛结束时的一个地方,而不是每个分支。

override def func[T <: Base](arg: T): T = (arg match {
  case A(i) => A(i)
  case B(s) => B(s)
}).asInstanceOf[T]

但请注意,您的设计本质上是不安全的,因为除了 BaseAB 之外还有 Base 的子类型:单例类型 (a.type)、复合类型 (A with SomeTrait)、 Null...,其中任何一个都可以用作T。重载可能会更好:

class Transform {
  def func(arg: Base): Base = arg match {
    case arg: A => func(arg)
    case arg: B => func(arg)
  }

  def func(arg: A): A = arg
  def func(arg: B): B = arg
}

class ExtTransform extends Transform {
  override def func(arg: A): A = A(arg.i + 1)
}

【讨论】:

  • 我没有处理 A with SomeTrait 匹配 A 的值。当然,这是一个问题。
【解决方案2】:

我建议改用 typeclass

sealed trait Base

object Base {
  final case class A() extends Base
  final case class B() extends Base

  sealed trait Builder[T <: Base] {
     def build(): T
  }

  object Builder {
     final implicit val ABuilder: Builder[A] = new Builder[A] {
       override def build(): A = A()
     }

     final implicit val BBuilder: Builder[B] = new Builder[B] {
       override def build(): B = B()
     }
  }
}

object Main extends App {
  def func[T <: Base](implicit builder: Base.Builder[T]): T =
    builder.build()

  func[Base.A] // res: Base.A = A()
  func[Base.B] // res: Base.B = B()
}

【讨论】:

  • 对不起,我的例子不恰当。正如我在编辑版本问题中提到的,我想使用定义的类作为父类并在子类中自定义匹配表达式。您的答案代码似乎无法做到这一点。
  • 抱歉拖了太久,写完就睡觉了。我没有看到将 typeclassessubtyping 重载 结合起来的简单方法。我认为阿列克谢的回答是你现在能做的最好的。 (附带说明,Dotty / Scala 3 白色宏将允许您毫无问题地编写原始代码)
  • @LuisMiguelMejíaSuárez inline def func[T &lt;: Base](arg: T) &lt;: T = inline arg match { case a : A =&gt; a; case b : B =&gt; b } 在 Dotty 0.17 和 Found: A(a) Required: T Found: B(b) Required: T 中对我来说仍然失败。
  • 啊,应该是inline def func[T &lt;: Base](arg: T) &lt;: Any = inline arg match { case a : A =&gt; a; case b : B =&gt; b } }val a: A = func(A(1))val b: B = func(B("a"))
猜你喜欢
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 1970-01-01
  • 1970-01-01
  • 2014-07-01
相关资源
最近更新 更多