【问题标题】:Can scala abstract type can hold the type parameter?scala抽象类型可以保存类型参数吗?
【发布时间】:2018-03-15 21:05:17
【问题描述】:

我有以下简单的演示代码,它给了我类型不匹配的错误。 我很不明白为什么会发生这个错误。谁能告诉我如何解决这个错误。进一步解释或相关的学习资源将不胜感激。

object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder[T] {
  type ElementType = T
}

class FooTypeHolder extends TypeHolder[Foo]

class Client[T <: TypeHolder[_]](val holder: T) {

  def showElementType(t: T#ElementType): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

编译错误为:

Error:(10, 26) type mismatch;
 found   : Application.foo.type (with underlying type Foo)
 required: _$1
  client.showElementType(foo)

2018 年 3 月 15 日更新

就像 Andrey Tyukin 会对 TypeHolder 的用途感到好奇一样,它除了 T 之外什么都没有。我在这个特征上添加了一些功能。

代码如下。

object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder[T] {
  type ElementType = T

  def holderFun(): Unit = println(s"holder, type:${getClass}")

  def elementFun(ele: ElementType): Unit = println(s"element, type:${getClass}")
}

class FooTypeHolder extends TypeHolder[Foo]

class Client[T <: TypeHolder[_]](val holder: T) {

  def showElementType(t: T#ElementType): Unit = {
    println("show element type " + t.toString)
    holder.holderFun()
    holder.elementFun(t)
  }

}

编译错误如下:

Error:(10, 26) type mismatch;
 found   : o2.Application.foo.type (with underlying type o2.Foo)
 required: _$1
  client.showElementType(foo)
Error:(30, 23) type mismatch;
 found   : _$1
 required: Client.this.holder.ElementType
    (which expands to)  _$1
    holder.elementFun(t)

总的来说,我想知道的核心是:

当我通过 val client = new Client(holder) 实例化 clientholder 的类型为:TypeHolder[Foo] scala编译器是否可以推断hoderElementType是“Foo”?

非常感谢@Andrey 提供各种可行的解决方案。 我检查了你的代码sn -p 1~3,并仔细对比了我的代码,这让我现在更清楚了。不过我还有一些问题。

版本 3

这个版本的代码最接近我原来的邮政编码。唯一的区别是函数showElementType的类型限制

  • 我的:def showElementType(t: T#ElementType): Unit
  • 您的:def showElementType(t: holder.ElementType): Unit

让我感到困惑的是,既然“(t: holder.ElementType)”有效,那么“(t: T#ElementType)”也应该有效吗?而实际上并非如此。为什么?

版本 2

这里的区别是type ElementType = T 的位置。我把它放在特征定义中,而你把它放在类型参数限制中。

  • 我的:trait TypeHolder[T] { type ElementType = T}
  • 您的:class Client[T, H &lt;: TypeHolder { type ElementType = T }]

我不明白为什么特征定义中的赋值语句不起作用。

版本 1

您使用两个类型参数TH 分别捕获FooTypeHolder 的类型。它应该绝对有效。客户端的类型也对应Client[Foo, TypeHolder[Foo]]。我的问题是,是否可以定义客户端仅使用一种类型参数,例如Client[TypeHolder[Foo]]。换句话说,编译器是否有可能从TypeHolder[Foo] 推断出ElementTypeFoo

谢谢。

【问题讨论】:

  • 这看起来像一个XY-problem,到目前为止,答案似乎不同,以至于人们可能会假设回答者试图随机猜测X-part。例如,我建议去掉TypeHolder,而@MarkoŠvaljek 建议不要使用Foo-typed foo 作为showElementType 的参数。尽管所有这些解决方案建议都可以编译,但生成的程序的含义似乎大不相同。需要进一步澄清预期的X

标签: scala


【解决方案1】:

我不确定您在这里尝试了什么,但这是我的“可能最接近可编译 Scala 的代码投影”的版本:

import scala.language.higherKinds
object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder[T] {
  type ElementType = T
}

class FooTypeHolder extends TypeHolder[Foo]

class Client[T, H[X] <: TypeHolder[X]](val holder: H[T]) {
  def showElementType(t: T): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

但也可能是这样的:

import scala.language.higherKinds
object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client[Foo, FooTypeHolder](holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder {
  type ElementType
}

class FooTypeHolder extends TypeHolder {
  type ElementType = Foo
}

class Client[T, H <: TypeHolder { type ElementType = T }](val holder: H) {
  def showElementType(t: T): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

或者这个?:

import scala.language.higherKinds
object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder[T] {
  type ElementType = T
}

class FooTypeHolder extends TypeHolder[Foo]

class Client[H <: TypeHolder[_]](val holder: H) {
  def showElementType(t: holder.ElementType): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

我对您的代码根本不了解的是:TypeHolder[T] 到底应该做什么?整个TypeHolder[T] 构造似乎不包含T 本身之外的任何信息,那么为什么不直接使用T,像这样呢?:

object Application extends App {
  private val foo = new Foo
  private val client = new Client[Foo]
  client.showElementType(foo)
}

class Foo

class Client[T] {
  def showElementType(t: T): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

一些一般提示:

  • 避免一次问多个问题
  • 避免在同一个帖子中就您上一个问题的答案之一提出多个问题。
  • 这可能需要一些繁重的编辑...

编辑

我将尝试按照您添加到帖子中的顺序或多或少相同的顺序回答所有其他问题:

1. 它适用于holder.ElementType,因为当您实例化Client 时,holder 的类型为TypeHolder[Foo],因此我们知道holder.ElementType = Foo

2. 它不适用于 T#ElementType for T &lt;: TypeHolder[_],因为正如签名中的存在性所说,T 可以是 TypeHolder forSome 未指定的 @987654339 @:

T <: TypeHolder[_$1] forSome { type _$1 }

T#ElementType 
  = (TypeHolder[_])#ElementType
  = (TypeHolder[_$1] forSome { type _$1 })#ElementType 
  = _$1

因此,T#ElementType 被设置为某种奇怪的合成存在类型 _$1,您会收到类似于以下内容的错误消息:

typeHolder.scala:6: error: type mismatch;
  found   : Main.foo.type (with underlying type Foo)
  required: _$1
  client.showElementType(foo)
                         ^

因此,将其设置为存在类型完全没有用,您将永远无法找到满足这种奇怪类型约束的术语。你甚至不能强制转换成这种类型,因为这种类型甚至没有可以在代码中的任何地方引用它的名称。

说实话:这似乎完全是垃圾。整个投影根本不应该编译,为什么世界上会有人想要一个存在量化的类型从它的量词下逃脱?这可能应该报告为编译器问题。

3.定义

class Client[T, H <: TypeHolder { type ElementType = T }]

之所以有效,是因为它还保持了 T 明确的类型。如果你换了 通过存在主义

class Client[T, H <: TypeHolder { type ElementType = X forSome { type X }}]

然后TTypeHolder 之间的每个连接都会丢失,并且它会失败的原因与上面的T#ElementType 非常相似:你只是不能用一些未知的存在替换元素类型,然后希望能把Foo传给它。

4. 关于“是否可以定义客户端只使用一种类型参数?” -- 当然,为什么不呢?

import scala.language.higherKinds
object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  client.showElementType(foo)
}

class Foo

trait TypeHolder {
  type ElementType
}

class FooTypeHolder extends TypeHolder {
  type ElementType = Foo
}

class Client[H <: TypeHolder](val holder: H) {
  def showElementType(t: H#ElementType): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

只是不要在TypeHolder 中添加任何类型参数,这样你以后就不必通过existentials 删除它们。

【讨论】:

  • 太棒了,你所有的代码都编译成功了。非常感谢您提供各种可行的解决方案。 TypeHolder 是简化版,我可以在上面添加一些功能。由于评论字数限制,我更新了我的问题。请您给我更多的指导。谢谢。
  • @user5698801 我已经大量修改了我的答案。我认为最后一个代码 sn-p 完全符合您的要求。您只是对所有类型参数和存在主义感到困惑。如果您不向TypeHolder 添加任何类型参数,然后不添加任何存在性,那么一切正常工作
  • 哇!!!你对存在类型的解释太棒了。我从你那里学到了很多,我也相信其他人可以从你的回答中受益。非常感谢您的耐心。这一定是正确的答案。
  • @user5698801 好的。你介意我的“重度耐心”添加一些“重度编辑技巧”,并稍微重写问题和答案吗?像现在这样,其他人很难从中受益,因为有相当多的不必要的蔓延。我将抛开一些不必要的题外话,并专注于导致解决方案的有趣部分(我希望这是解决方案,对吗?)。
【解决方案2】:

经过一些修改后编译:

object Application extends App {
  private val foo = new Foo
  private val holder = new FooTypeHolder
  private val client = new Client(holder)
  // not sure why you try to use this one
  // client.showElementType(foo)
  // foo doesn't extend the Typeholder, it's just regular class

  // on the other hand this will compile
  client.showElementType(holder)
}

class Foo

trait TypeHolder[T] {
  type ElementType = T
}

class FooTypeHolder extends TypeHolder[Foo]

class Client[T <: TypeHolder[_]](val holder: T) {

  // no need to use the #ElementType
  def showElementType(t: T): Unit = {
    println("show element type " + t.toString)
    println(t.getClass)
  }
}

希望这也有帮助:

What does the `#` operator mean in Scala?

https://github.com/ghik/opinionated-scala/wiki/Generics-and-type-members

就错误 _$1 而言,它只是编译器说:“我无法弄清楚这个”

【讨论】:

  • 如果假设没有必要使用ElementType,那么一开始的整个问题是什么?在完全不受约束的类型 T 的参数上调用 .toString.getClass?它不会以任何重要的方式使用ElementTypeTypeHolder,它会将holder 本身而不是foo 传递给showElementType,而foo 仍然是一个未使用的变量。
  • 嗨,安德烈,感谢您的评论。老实说,我试图提供有关 # 的更多资源,并表明在这种情况下甚至可能不需要它
  • 链接的资源没问题。我只是想知道您为什么决定更改 App 中的代码:我假设 App 中的代码代表了 OP 想要实现的目标,并且围绕它的整个类型级计算只是为了支持App 中的代码。但是,确实,很难说这是否正是 OP 想要的……我已经要求进一步澄清这个问题。
  • 嗨,Marko,感谢您提供更多资源。但是,将holder 传递给client.showElementType() 似乎也无法编译。
  • 没问题,我还对方法进行了一些修改以使其编译(我提供的代码编译)。同样,我不能 100% 确定代码的初衷,所以我只是选择了第一种合理的方法。我也发现安德烈的答案非常好;)
猜你喜欢
  • 2015-12-24
  • 2018-08-01
  • 1970-01-01
  • 2014-12-01
  • 1970-01-01
  • 2021-03-17
  • 1970-01-01
  • 1970-01-01
  • 2012-07-10
相关资源
最近更新 更多