【问题标题】:flatMap on Map with wildcard type parameter带有通配符类型参数的 Map 上的 flatMap
【发布时间】:2023-03-03 04:42:01
【问题描述】:

我正在尝试写这样的东西:

trait Typed[T]

trait Test {

  def testMap: Map[Typed[_], Int]

  def test = testMap.flatMap {case (typed, size) => Seq.fill(size)(typed)}
}

但我收到以下错误:

error: no type parameters for method flatMap: (f: ((Typed[_], Int)) => Traversable[B])(implicit bf: scala.collection.generic.CanBuildFrom[scala.collection.immutable.Map[com.quarta.service.querybuilder.Typed[_],Int],B,That])That exist so that it can be applied to arguments (((Typed[_], Int)) => Seq[Typed[_0]] forSome { type _0 })
--- because ---
argument expression's type is not compatible with formal parameter type;
found   : ((Typed[_], Int)) => Seq[Typed[_0]] forSome { type _0 }
required: ((Typed[_], Int)) => Traversable[?B]
def test = testMap.flatMap {case (typed, size) => Seq.fill(size)(typed)}

如果将 testMap 类型更改为以下代码,则此代码有效:

def testMap: Map[Typed[Any], Int]

有什么区别以及如何解决我的问题?

【问题讨论】:

  • 为什么需要通配符而不是Any?您希望如何使用此特征?
  • 我展示了这个特征作为例子。问题是我必须编写采用 Map[Typed[],Int] 并根据大小变量将其转换为 Seq[Typed[]] 的方法。
  • 但是Map[Typed[_],Int] 会有一些类型。为什么不参数化它,Map[Typed[A],Int] 而不是通配符呢?
  • 因为这个地图会包含不同类型的元素(Typed[T1], Typed[T2]...Typed[TN])。

标签: generics scala types scala-collections


【解决方案1】:

参数化测试不是一个选项吗?

trait Test [A] {
   def testMap: Map [Typed [A], Int]
   def test = testMap.flatMap {case (typed, size) => Seq.fill (size)(typed)}
}

在知道 A 是什么之前,您必须声明 Test 吗?

【讨论】:

    【解决方案2】:

    我认为问题在于您正在尝试将匿名函数与存在类型参数进行模式匹配。

    来自语言规范,第 8.5 节关于模式匹配匿名函数:

    必须部分定义此类表达式的预期类型。它 对于某些k > 0,必须是scala.Functionk[S1, ... , Sk, R],或者 scala.PartialFunction[S1, R], 参数type(s) S1, ... , Sk 必须完全确定,但结果类型R可能是 未定。

    testMap 是一种存在类型(参见语言规范 3.2.10)。存在类型具有 T forSome {Q} 的形式,其中 Q 是类型声明的序列。您使用了特殊的占位符语法,因此 Map[Typed[_], Int] 类型等同于 Map[Typed[t] forSome { type t }, Int],这可能会使错误消息更有意义。

    至于解决方案,我想这完全取决于您要做什么,而您没有说... :)

    【讨论】:

    • 我注意到我们实际上并不是模式匹配(不是match,只是使用case 的部分函数),所以这可能不正确,但可能是出于相同原因的问题不适用于模式匹配,
    【解决方案3】:

    如果我正确理解您的问题,答案是:如果TypedT 中是协变的,即trait Typed[+T],您可以这样做。

    示例

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    
    class Typed[+T: Manifest] {
      override def toString = "Typed[" + implicitly[Manifest[T]].toString + "]"
    }
    
    trait Test {
      def testMap: Map[Typed[_], Int]
    
      def foo = testMap flatMap { case (t, s) => Seq.fill(s)(t) }
    }
    
    val bar = new Test { 
      def testMap = Map(new Typed[Double]() -> 3, new Typed[Int]() -> 5)
    }
    
    // Hit Ctrl-D
    
    scala> bar.foo
    res0: scala.collection.immutable.Iterable[Seq[Typed[Any]]] = List(Typed[Double], Typed[Double], Typed[Double], Typed[Int], Typed[Int], Typed[Int], Typed[Int], Typed[Int])
    

    请注意,我在此示例中创建了 Typed 一个类以获得更好的输出。您当然可以使用trait

    现在,为什么这里需要协方差?

    协方差基本上意味着如果A <: B 那么X[A] <: X[B]。因此,如果您将 testMap 声明为 Map[Typed[Any], Int]Typedinvariant,则不允许传入例如Typed[Double] 用于 Typed[Any],即使 Double <: Any。在这里,scala 编译器似乎在协变情况下将 _ 替换为 Any(有关详细信息,请参阅 extempore 的评论)。

    关于下划线问题的解释,我会参考 Luigi 的回答。

    【讨论】:

    • +1 好答案。如果有人想知道,T 只需要一个 Manifest 来支持toString,所以你可以在没有它的情况下完成这项工作。
    • 感谢 fotNelton 的回复。但不幸的是,我不能使 Typed 类型协变,因为 Typed 实际上是案例类:案例类 Typed[+T](fun: T => Any) 其中类型 T 出现在逆变位置。
    • 您可以像这样解决这个问题:case class Typed[+T, S <: T](fun: S => Any),然后调整示例代码的其余部分以支持两个类型参数。 HTH!
    • 重新阐述:SLS 3.2.10,存在简化规则,规则#4。
    • 感谢即兴解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-10
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    • 2017-11-06
    相关资源
    最近更新 更多