具有类型变量的类型模式的用例和示例

发布于 2024-12-03 05:35:02 字数 983 浏览 0 评论 0原文

我发现在进行类型模式匹配时,阅读了 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)

I found out reading the spec that scala supports binding type variables when doing a type pattern match:

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(", "))
}

Are there any fancy things or practical things I can do with this? Or binding type variables is only useful for type documentation purpose?

It occurred to me that Scala sometimes needs type annotations, such as defining function, so I tried:

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

But then the type of the return function is weird:

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

千纸鹤带着心事 2024-12-10 05:35:02

我希望这不会太长,但我严重怀疑这一点,这就是为什么我要首先尝试提供一个快速答案:“当你命名(抽象)某个东西时,主要用例是稍后引用它”。好吧,那现在没用了,不是吗?

考虑这个简单的 Scala 函数:

val sum = (a: Int, b: Int) => a + b

编译器不需要知道 aabb >。它只需要知道 ab 都是 Int 类型,并且 a 位于 之前>b (在这种情况下这并不重要,因为加法是可交换的,但编译器无论如何都会关心!)。 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 的通配符类型。
这取自 Programming in Scala - Existential Types 并由您稍加修改真的。

// 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[_]

I hope this won't get too long, but I seriously doubt it, that's why I'm gonna try to provide a quick answer first: "When you name (abstract) something, the main use case is referring to it later". Well that wasn't helpful now, was it?

Consider this simple Scala function:

val sum = (a: Int, b: Int) => a + b

The compiler does not need to know that a is an a and b is a b. All it needs to know that a as well as b are of type Int and that a comes before b (which wouldn't matter in this case since addition is commutative, but the compiler cares anyway!). Scala offers a (don't get me wrong I also love it) compiler friendly placeholder syntax, which acts as a proof of this "hypothesis".

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

Now take a look at this:

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

When you don't care about the type argument use the placeholder syntax. When you do (for whatever reason) care you should name the type argument with a lower case so the compiler knows you want to treat it as an identifier.

Back to your question.

The primary use for existential types is working around Java's wildcard types.
This is taken from Programming in Scala - Existential Types and was slightly modified by yours truly.

// 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
}

Ok, so what just happened? Simple generics, no magic there?! If you are dealing with generics on a day to day basis this looks normal to you, but you are forgetting, that the ultra super concept of introducing type arguments into scope works only on classes and methods. What if you are outside of a class or a method, just in some random scope in the middle of nowhere (like REPL)? Or what if you are in a class or a method but the type arguments have not been introduced into their scopes? This is where your question and this answer come in play.

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
  }
}

The identifier kind is required so the compiler can verify that you are referring to the same thing.

Note, that you can't just add strings into the set since the type of the set is Set[_].

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文