【问题标题】:Why are type synonyms necessary to define extractors for patten matching Java classes in Scala?为什么在 Scala 中为模式匹配 Java 类定义提取器需要类型同义词?
【发布时间】:2013-03-21 14:32:28
【问题描述】:

如果存在现有的 Java 类 pkg.Test,

package pkg;
public class Test {
  public int v;
  public Test(int v) { this.v = v; }
}

试图通过定义一个 scala 伴随对象来创建一个提取器

package pkg
object Test{
  def unapply(t : Test) : Option[Int] = Some(t.v)
}

产生错误“测试已定义为对象测试”。 但是,如果我在新包中为 java 类创建同义词,一切似乎都正常

package pkg
package object matchers {
  type Test = pkg.Test
}

并在新包中定义对应的对象

package pkg.matchers
object Test {
  def unapply(t : Test) : Option[Int] = Some(t.v)
}

现在模式和原始类的所有成员都可以从新包中获得

import pkg.matchers._
object main {
  def main(args : Array[String]) {
    val t = new Test(1)
    t match {case Test(v) => println(v)}
    println(t.v)
  }
}

添加一个类型同义词让这个工作看起来很奇怪。除了必须使用新包之外,以这种方式添加模式匹配是否有任何问题?有什么办法可以使提取器在原始包中可用?

【问题讨论】:

    标签: scala pattern-matching


    【解决方案1】:

    这是因为在与 Java 库交互时,Scala 需要一种将 Java 的静态方法映射到 Scala 的单例对象的方法。

    例如,如果您有以下 Java 类:

    package pkg;
    
    class Test {
      public static void printHello() {
        System.out.println("Hello");
      }
    }
    

    我们如何在 Scala 中调用printHello?它将如下所示:

    import pkg.Test
    Test.printHello()
    

    如您所见,语法与调用object Test 中的方法完全相同。从这点来看,Test 单例已经定义好了,你不能用相同的 FQN 定义一个object 两次。

    这就是为什么您需要定义pkg.matchers.Test,这样它就不会与pkg.Test 冲突。

    Scala 的编译器足够聪明,可以判断出pkg.matchers.Test 是一个无法使用new 关键字构造它的单例,因此当您编写new Test(1) 时,它是pkg.Test 而不是pkg.matchers.Test。这就是您可以在代码示例中同时使用它们的原因。

    实际上,您根本不需要type Test = pkg.Test,以下工作正常:

    package pkg
    
    package matchers {
      object Test {
        def unapply(t : Test) : Option[Int] = Some(t.v)
      }
    }
    
    object main {
    
      import pkg.matchers._
    
      def main(args : Array[String]) {
    
        val t = new Test(1)
        t match {case Test(v) => println(v)}
        println(t.v)
      }
    }
    

    更新

    Extractor 不需要是伴生对象,这意味着您不需要相应的类。任何带有unapply 方法的object 都可以用作提取器。

    object StringLength {
      def unapply(x: String): Option[Int] = Some(x.length)
    }
    
    object Main {
    
      def main(args: Array[String]) {
    
        "Hello World" match {
          case StringLength(x) => println("length:" + x)
        }
      }
    }
    

    所以如果main方法不在pkg中,你有以下选择:

    1. 将您的提取器重命名为另一个名称,以便编译器知道您正在使用提取器。

    2. 使用 FQN,因此编译器知道您访问的是 pkg.matchers.Test 而不是 pkg.Test

      package pkg.matchers {
      
        import pkg._
      
        object Test { 
          def unapply(t : Test) : Option[Int] = Some(t.v)
        } 
      }
      
      object Main {
      
        def main(args: Array[String]) {
      
          import pkg.Test
      
          val t = new Test(1)
          t match {case pkg.matchers.Test(v) => println(v)}
          println(t.v)
      
        }
      
      }
      
    3. 使用import 语法将pkg.Test 重命名为另一个名称,这样它就不会与pkg.matchers.Test 冲突。

      object Main {
      
        def main(args: Array[String]) {
      
          import pkg.{Test => JTest}
          import pkg.matchers.Test
      
          val t = new JTest(1)
          t match {case Test(v) => println(v)}
          println(t.v)
      
        }
      }
      
    4. 只需从pkg 导入所有内容,特别是导入pkg.matcher.Test

        def main(args: Array[String]) {
      
          import pkg._
          import pkg.matchers.Test
      
          val t = new Test(1)
          t match {case Test(v) => println(v)}
          println(t.v)
      
        }
      

    【讨论】:

    • 如果 main 不在 pkg 中或之下,这将如何工作?我认为与 pkg.Test 的静态部分不冲突很重要,并且提取器实际上不是伴随对象,但范围界定令人困惑。将 pkg.Test 的显式导入添加到 main 的开头会产生一个错误,即模式中的“测试”不明确,因为它被导入了两次,而以某种方式从同一个包中获取它是有效的。
    • 只有您的第四个选项可以满足我的要求 - 让模式和原始类都无法访问。我在规范中没有看到任何解释为什么 import pkg._ 应该与显式导入所有名称有任何不同,但是将行更改为 import pkg.Test 会产生与以前相同的变量。不过,这可能是另一个问题。感谢您对此的帮助。
    猜你喜欢
    • 2022-06-24
    • 2021-12-08
    • 2020-10-02
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多