Scala,为什么我不需要导入推导类型
Scala, why do I not need to import deduced types
我觉得我应该以我正在使用 sbt 构建我的项目这一事实作为开头。
我的问题是,如果在编译时某个方法 return 是某种未导入的类型,在我调用该方法的文件中,只要我使用类型推断,一切都会编译。一旦我尝试将未导入的类型分配给我使用函数的 return 值创建的 var/val,就会出现编译器错误。
假设我在两个包中有两个 classes。 Class App
包 main
和 class Imported
包 libraries
中。让我们进一步说我们在包 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++ 中的动态转换无关。代码中唯一起作用的机制是类型推断。
我觉得我应该以我正在使用 sbt 构建我的项目这一事实作为开头。
我的问题是,如果在编译时某个方法 return 是某种未导入的类型,在我调用该方法的文件中,只要我使用类型推断,一切都会编译。一旦我尝试将未导入的类型分配给我使用函数的 return 值创建的 var/val,就会出现编译器错误。
假设我在两个包中有两个 classes。 Class App
包 main
和 class Imported
包 libraries
中。让我们进一步说我们在包 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++ 中的动态转换无关。代码中唯一起作用的机制是类型推断。