使用类型变量的类型模式的用例和示例

12

我在查阅规范时发现,Scala 支持在进行类型模式匹配时绑定类型变量:

Map(1 -> "one", 2 -> "two") match {
  case l: Map[k, v] =>
    // binds k to Int and v to String
    // k and v are types as shown here:
    val i: Iterator[Tuple2[k, v]] = l.iterator
    println(i.mkString(", "))
}

我能用这个做一些花里胡哨或者实用的东西吗?或者只有绑定类型变量用于类型文档的目的?

我想到了在Scala中有时需要类型注释,例如定义函数,所以我尝试了以下代码:

def prepender(obj: Any) = obj match {
  case xs: List[a] => (x: a) => x :: xs
  case opt: Some[a] => (x: a) => x :: Nil
}

但是返回函数的类型很奇怪:

prepender: (obj: Any)a with a => List[Any] forSome { type a; type a }

scala> val p = prepender(List(1,2))
p: a with a => List[Any] forSome { type a; type a } = <function1>

scala> p(1)
<console>:10: error: type mismatch;
 found   : Int(1)
 required: a(in value res7) with (some other)a(in value res7) where 
   type (some other)a(in value res7), type a(in value res7)

起初我认为这只是一种语法上的便利,因为类型总是在编译时已知。但现在我在想,这是否可以用于实例化存在类型? - Kipton Barros
1个回答

13
希望这不会太长,但我严重怀疑这一点,这就是为什么我要先尝试提供一个快速回答:“当你给某个东西(抽象)命名时,主要用途是稍后引用它”。好吧,这并不是很有帮助,对吧?
考虑这个简单的Scala函数:
val sum = (a: Int, b: Int) => a + b

编译器不需要知道 a 是一个 a,也不需要知道 b 是一个 b。它只需要知道 ab 都是 Int 类型,并且 ab 之前(虽然在这种情况下加法是可交换的,但编译器仍然关心!)。Scala 提供了一种(不要误解,我也很喜欢它)编译器友好的占位符语法,它作为这个“假设”的证明。
val sum: (Int, Int) => Int = _ + _ // where the 1st _ differs from the 2nd _

现在看看这个:
case x: SomeTypeParameterizedWith[AnotherType] // AnotherType is erased anyway
case x: SomeParameterizedType[_] // Existential type
case x: SomeParameterizedType[kind] // Existential type which you can reference

当您不关心类型参数时,请使用占位符语法。当您(出于任何原因)关心时,应使用小写字母为类型参数命名,以便编译器知道您希望将其视为标识符。
回到你的问题。
存在类型的主要用途是解决Java通配符类型的问题。 这段文字摘自Scala编程 - 存在类型,并经过了略微修改。
// This is a Java class with wildcards
public class Wild {
  public java.util.Collection<?> contents() {
    java.util.Collection<String> stuff = new Vector<String>();
    stuff.add("a");
    stuff.add("b");
    stuff.add("see");
    return stuff;
  }
}

// This is the problem
import scala.collection.mutable.Set
val iter = (new Wild).contents.iterator
val set = Set.empty[???] // what type goes here?
while (iter.hasMore)
  set += iter.next()

// This is the solution
def javaSet2ScalaSet[T](jset: java.util.Collection[T]): Set[T] = {
  val sset = Set.empty[T] // now T can be named!
  val iter = jset.iterator
  while (iter.hasNext)
    sset += iter.next()
  sset
}

好的,刚才发生了什么?简单的泛型,没有魔法在那里?!如果您每天都在处理泛型,那么这看起来很正常,但是您忘记了,将类型参数引入范围的超级概念仅适用于类和方法。如果您在类或方法之外,在某个随机范围内(例如REPL)怎么办?或者,如果您在类或方法中,但类型参数尚未引入它们的范围,则该怎么办?这就是你的问题以及这个答案发挥作用的地方。
val set = new Wild().contents match {
  case jset: java.util.Collection[kind] => {
    val sset = Set.empty[kind]
    val iter = jset.iterator
    while (iter.hasNext)
      sset += iter.next()
    sset
  }
}

标识符 kind 是必须的,这样编译器才能验证你正在引用相同的东西。
需要注意的是,你不能仅仅将字符串添加到 set 中,因为 set 的类型是 Set[_]

如果你有时间的话,能否提供一些关于如何实现这个功能的代码? - huynhjl
给你。希望它有所帮助。 - agilesteel
将“val sset = Set.empty[kind]”更改为“val sset = Set.empty[Any]”,无需任何类型模式。 - Eduardo Pareja Tobes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接