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

huy*_*hjl 12 types scala pattern-matching

我发现在执行类型模式匹配时读取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(", "))
}
Run Code Online (Sandbox Code Playgroud)

我能做些什么花哨的东西或实用的东西吗?或绑定类型变量仅用于类型文档目的?

我想到Scala有时需要类型注释,比如定义函数,所以我试过:

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

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

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)
Run Code Online (Sandbox Code Playgroud)

agi*_*eel 13

我希望这不会太长,但我严重怀疑它,这就是为什么我要先尝试提供一个快速的答案:"当你命名(抽象)某些东西时,主要的用例是指之后的".嗯现在没用,是吗?

考虑一下这个简单的Scala函数:

val sum = (a: Int, b: Int) => a + b
Run Code Online (Sandbox Code Playgroud)

编译器不需要知道它a是a a并且b是a b.它只需要知道,a以及b是类型Inta到来之前b(因为加法是可交换的,但不管怎么说,编译器关心这不会在这种情况下重要!).Scala提供了一个(不要误解我也喜欢它)编译器友好占位符语法,它充当了这个"假设"的证明.

val sum: (Int, Int) => Int = _ + _ // where the 1st _ differs from the 2nd _
Run Code Online (Sandbox Code Playgroud)

现在来看看这个:

case x: SomeTypeParameterizedWith[AnotherType] // AnotherType is erased anyway
case x: SomeParameterizedType[_] // Existential type
case x: SomeParameterizedType[kind] // Existential type which you can reference
Run Code Online (Sandbox Code Playgroud)

如果不关心type参数,请使用占位符语法.当你(无论出于何种原因)关心时,你应该用小写字母命名类型参数,这样编译器就知道你想把它当作一个标识符.

回到你的问题.

存在类型的主要用途是解决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
}
Run Code Online (Sandbox Code Playgroud)

好吧,那刚刚发生了什么?简单的泛型,没有魔法吗?!如果你每天都在处理泛型,这看起来很正常,但是你忘记了,将类型参数引入范围的超级概念仅适用于类和方法.如果你不在一个类或一个方法之外,只是在一个随处可见的范围内(如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
  }
}
Run Code Online (Sandbox Code Playgroud)

标识符kind是必需的,因此编译器可以验证您是指同一件事.

请注意,你不能只是字符串添加到set自类型setIS Set[_].