Scala在通用特征方法中找不到案例类参数

Posted

技术标签:

【中文标题】Scala在通用特征方法中找不到案例类参数【英文标题】:Scala can't find case class parameter in generic trait method 【发布时间】:2021-07-05 09:37:48 【问题描述】:

我有以下用通用方法实现特征的案例类。目标是让每个扩展 trait 的类 S 以这样的方式实现方法,即该方法使用 S 类型的参数并返回 S 类型的值。

case class Foo(x: Int) extends Bar 
  def baz[Foo](v: Foo): Foo = 
    Foo(v.x + x)
  


trait Bar 
  def baz[S <: Bar](v: S): S

但是,在编译这个时,我得到以下错误,x 找不到。

error: value x is not a member of type parameter Foo
    Foo(v.x + x)
          ^
one error found

对我来说更奇怪的是,当我将方法更改为返回 Foo(3)(从而删除对 x 的访问)时,我收到以下错误:

error: type mismatch;
 found   : <empty>.Foo
 required: Foo(in method baz)
    Foo(3)
       ^
one error found

我不确定&lt;empty&gt;.FooFoo(in method baz) 是什么类型,以及为什么这两个类型不只是Foo。显然,我对 Scala 的类型系统有一些误解,因此不胜感激。

编辑

我希望有多个类实现Bar。我希望能够在 Bar 类型的 val 上调用 baz

考虑在下面的示例中,ab 是从其他地方获得的,根据我的程序逻辑,我知道它们都是同一类的实例(不一定是 Foo)。在这种情况下,有什么方法可以合法拨打baz 吗?我知道我可以将两者都转换为Foo,但这发生在我不知道特定类是什么的情况下。也就是说,我知道ab 都是T &lt;: Bar 类型的T,但我不知道T 是什么。

object Foo 
  def main(args: Array[String]): Unit = 
    val a: Bar[_] = Foo(3)
    val b: Bar[_] = Foo(2)
    a.baz(b) // This call is invalid
  

【问题讨论】:

顺便说一句,this answer 对您较早的问题提出的建议与我所做的大致相同,并且您接受了它。这对你不起作用吗? @OriginalOriginalOriginalVI 我想我没有看到这两者之间的联系,但我认为你是对的,它们基本上是同一件事。也许这应该只是作为副本关闭。 我建议在这种情况下使用类型类(尽管它们不适用于您的示例,因为您在那里使用了通配符)。如果它不能解决您的问题,我也建议不接受我的答案(我正在编辑它,但在那之前其他人可能会回答) 您的示例似乎太复杂了。我会选择 F[_],它会更简单地解决您的问题 @user2963757 我不明白你的建议。 【参考方案1】:

baz 的类型参数不像你想象的那样工作。在原始特征 Bar 中,它告诉您可以传入 any S,扩展 Barbaz 将输出另一个 S。这意味着在Foo 中,您不仅要处理Foo,还要处理继承自Bar 的其他类。

此外,类型参数Foo 隐藏类Foo。相当于这样:

case class Foo(x: Int) extends Bar 
  def baz[T](v: T): T = 
    Foo(v.x + 1)
  

TFooBar完全无关,所以你的方法也不满足baz的签名,因为它应该是T &lt;: Bar

我认为您想要做的是通过将类型参数赋予 Bar 本身来获得 S(这称为 F 有界多态性):

case class Foo(x: Int) extends Bar[Foo] 
  def baz(v: Foo): Foo = 
    Foo(v.x + 1)
  


trait Bar[S <: Bar[S]]  this: S =>
  def baz(v: S): S

这里S基本上可以用来指代扩展Bar的类的类型,所以编译器知道baz输入和输出Foos。

你以后可以像这样使用它:

def test[T <: Bar[T]](a: T, b: T) = a.baz(b)

不过,根据您的编辑,您似乎想要一个类型类。我建议将Bar 的定义更改为:

trait Bar[T] 
  def baz(t: T): T

那么Foo可以这样定义:

case class Foo(x: Int)

FooBar 的隐式实例将单独提供(可能在 Bar 的伴随对象中):

object Bar 
  implicit val fooBar: Bar[Foo] = new Bar 
    def baz(v: Foo): Foo = Foo(v.x + 1)
  
  //For convenience
  def apply[T](implicit bar: Bar[T]): bar

你以后可以像这样使用它:

def test[T : Bar](a: T, b: T) = Baz[T].bar(a, b)

请参阅Advantages of F-bounded polymorphism over typeclass for return-current-type problem 了解更多信息。

【讨论】:

第二个给我trait Bar takes type parameters @GuruStron 我的错,让我修复它。 鉴于类型参数现在与类相关联,我不知道如何使用我知道的两个 Bar 类型的 val 调用 baz实际上是同一类的实例,而无需将两者都转换为特定类(例如Foo)。 @MichaelMior 我不明白。您是否想要Foo 处理Bar 的任何子类型S?如果是这样,我可以更改答案,但您能否编辑您的问题以更好地解释您的意思? @OriginalOriginalOriginalVI 感谢您的耐心等待。我编辑了我的答案以试图澄清。

以上是关于Scala在通用特征方法中找不到案例类参数的主要内容,如果未能解决你的问题,请参考以下文章

在 Laravel 5 单元测试中找不到特征

在 Android Studio 插件列表中找不到 sbt

无法在Postgres中实例化外部Hive Metastore /在类路径中找不到驱动程序

在 Android Studio 项目中找不到参数的方法 android()

java运行中找不到main方法

在flutter中找不到参数android()的方法