Scala,为什么我不需要导入推导类型

Scala, why do I not need to import deduced types

我觉得我应该以我正在使用 sbt 构建我的项目这一事实作为开头。

我的问题是,如果在编译时某个方法 return 是某种未导入的类型,在我调用该方法的文件中,只要我使用类型推断,一切都会编译。一旦我尝试将未导入的类型分配给我使用函数的 return 值创建的 var/val,就会出现编译器错误。

假设我在两个包中有两个 classes。 Class Appmain 和 class Importedlibraries 中。让我们进一步说我们在包 main 中有一个 class ImportedFactory 并且这个 class 有一个创建类型为 Imported.

的对象的方法

这段代码编译得很好:

class App() {
    // method return object of type Imported
    val imp = ImportedFactory.createImportedObject() 
}

这不是:

class App() {
    // method return object of type Imported
    val imp : Imported = ImportedFactory.createImportedObject() 
}

这又是:

import libraries.Imported

class App() {
    // method return object of type Imported
    val imp : Imported = ImportedFactory.createImportedObject() 
}

这看起来很奇怪。这对于在编译时具有类型推断的语言来说是正常的吗,由于我的无知,我直到现在才在 go/C++ 中注意到它?

两种有效方法(导入和显式类型与推断)中的一种是否比另一种更 advantages/drawback? (当然,期待一个更明确和冗长,另一个更短)

这是黑魔法还是 Scala 编译器以相当直接的方式完成这些推导?

注意:使用 type inference

一词会得到更好的搜索结果

使用 val imp = ImportedFactory.createImportedObject(),您可以让编译器根据 类型推断 确定 imp 的类型。 createImportObject returns 是什么类型,imp 就是什么类型。

对于 val imp : Imported = ImportedFactory.createImportedObject(),您明确声明 imp Imported。但是编译器不知道你的意思,除非你......导入......它。

两种方法都有优点:

推断类型

推断类型非常适合将类型应该很明显的代码放在一起:

val i = 1 // obviously `i` is an int
val j = i + 10 // obviously still an int

这对于本地 vars/vals 来说也很棒,因为写这个类型太麻烦了

val myFoo: FancyAbstractThing[TypeParam, AnotherTypeParam[OhNoMoreTypeParams]] = ...
// vs
val myFoo = FancyThingFactory.makeANewOne()

缺点是,如果您允许 public def/val 具有推断类型,则可能更难确定如何使用该方法。出于这个原因,省略类型注释通常仅用于简单常量,并且在本地 vals/vars 中 "client code" 不必查看。

显式类型

当您确实想要编写类似库的代码(即 public vals/defs)时,惯例是显式键入它们。

最简单的原因可能是因为:

def myLibraryMethod = {
  // super complicated implementation
}

更难理解
def myLibraryMethod: String = {
  // super complicated implementation
}

显式键入代码的另一个好处是,当您想公开比实际值更不具体的类型时:

val invalidNumbers: Set[Int] = TreeSet(4, 8, 15, 16, 23, 42)

在此示例中,您不希望客户端代码需要关心您的 invalidNumbers 实际上是 TreeSet。那是一个实现细节。在这种情况下,您隐藏了一些信息,虽然这些信息是真实的,但会分散注意力。

导入的唯一作用是使非完全限定名称在当前范围内可用。你也可以这样写:

class App() {
    val imp: libraries.Imported = ImportedFactory.createImportedObject() 
}

import libraries.Imported 的原因是为了让更短的名字 Imported 可供你书写。如果让编译器推断类型,则无需在代码中提及类型,因此不必导入其较短的名称。

顺便说一句:这与 C++ 中的动态转换无关。代码中唯一起作用的机制是类型推断。