在Scala中,`=> String`的类型是什么?

4
在Scala中,有一些称为按名称调用参数的参数:
def hello(who: => String) = println("hello, " + who)

参数 who 的类型是什么?

在 Scala REPL 中,它显示为以下函数:

hello: (who: => String)Unit

这种类型仍然是=> String吗?它有任何名称吗?或者有一些文档来描述这种类型吗?

answer引发的进一步问题

问题1

(阅读§3.3.1规范(方法类型)时)

方法类型是方法的类型,比如我定义了一个方法hello

def hello: String = "abc"

这个类型可以写成:=> 字符串,对吧?虽然你可以看到REPL的响应是:
scala> def hello:String = "abc"
hello: String

如果我定义了一个带有参数的方法:
def goodname(name: String): String = name + "!"

这个方法的类型是什么?它应该类似于 String => String,但不完全相同。因为它是一个方法类型,而 String => String 是一个函数类型。

问题2

(阅读 §3.3.1(方法类型)规范时)

我可以理解为:

def goodname(name: String): String = name + "!"
def print(f: String => String) = println(f("abc"))
print(goodname)

当我调用print(goodname)时,goodname的类型会转换为函数类型String => String,对吗?
但是对于无参数方法:
def hello: String = "abc"

它可以转换为哪种函数类型?我尝试了:

def print(f: () => String) = println(f())

但这无法编译:
print(hello)

错误信息为:

错误:类型不匹配; 找到的是:String 需要的是:() => String

你能给我一个可行的例子吗?

问题3

(当阅读§6.26.2规范(方法转换)时)

只有在参数未应用类型时才会发生评估转换。因此,对于代码:

def myname:String = "abc"
def print(name: => String) = println(name)
print(myname)

我的问题是,当我调用print(myname)时,是否发生了转换(我指的是评估转换)?我猜想,由于myname的类型只是=> String,因此可以直接传递给print
如果print方法已更改:
def myname:String = "abc"
def print(name: String) = println(name)
print(myname)

这里肯定发生了“评估转换”,对吗?(从=> String 转换为 String


似乎类型不是 ()=>String,因为 hello(() => "Freewind") 无法编译。 - Freewind
在你的例子中,myname 的类型是什么? - Sean Vieira
@SeanVieira,已修复并改进。 - Freewind
回复:问题1 - 方法不是值(§3.3),因此它们本身没有类型。通过在FunctionN中包装对它们的调用,将它们转换为值,其中apply方法调用底层方法。因此,当您将goodname传递给print时,您正在向print传递Function1 [String,String]的实例,该实例调用goodnameprint(new Function1[String, String] { def apply(v: String) = goodname(v) }) - Sean Vieira
回复:问题2 - 无参数方法将被转换为Function0[T]。你遇到的问题是因为编译器正在调用hello(因为这样的方法“每次引用无参数方法名称时都会重新评估”)。你必须明确告诉编译器使用_来推迟评估,而不是print(hello),应该是print(hello _) - Sean Vieira
回复:问题3 - 在您的第一个示例中,当表达式println(name)中引用name时,会发生评估转换。在您的第二个示例中,是的,您是正确的,myname在调用站点进行评估。print(myname)print(myname()),即print("abc") - Sean Vieira
1个回答

7

引用自规范第4.6.1节:

这种参数的类型是无参方法类型=> T

因此,按名称调用参数的类型是(大约)() => T(或者如果您喜欢,则为Function0[T])。 如果您对接受按名称调用参数的方法进行:javap,则会看到编译后代码接受scala.Function0<java.lang.Object>类型的参数。

近似例子

以下是该翻译的一个示例:

def callByName[T](f: => T) = f

callByName { /* magic */
    1 + 1
/* ends here */ }

实际上是:

def callByName[T](f: Function0[T]) = f.apply()

callByName(new Function0[Int] {
  def apply() = { /* magic */
    1 + 1
  /* ends here */ }
})

关于函数近似的疑惑

你可能会尝试将一个 () => T 传递给你的方法。尝试 callByName(() => 12);为什么它不能编译?(提示:考虑调用站点处的扩展)。 (将鼠标悬停在下面的空白处以查看答案):

callByName(() => 12) 不能编译的原因是由于扩展被视为:

callByName(new Function0[() => Int] { def apply() = () => 12 })
这意味着,与其传递返回一个 IntFunction0,你正在传递返回一个返回 IntFunction0

=> T 实际上是什么

=> T 实际上是一种方法类型,而不是对象。因此,前面的所有内容都是编译器的近似值,并且可以随时更改。引用来自 §3.3 的描述:

以下解释的类型不表示值集,也不在程序中显式出现。它们在本报告中作为定义的标识符的内部类型引入。

那么方法类型是什么?引用来自 §3.3.1(MethodTypes)

特殊情况是没有任何参数的方法类型。它们写成 => T。无参数方法名称指定每次引用无参数方法名称时重新评估的表达式。

方法类型不存在于值类型中。如果将方法名称用作值,则隐式将其类型转换为相应的函数类型 (§6.26)。

§6.26.3 (MethodConversions)则说明:

下面四种隐式转换可以应用于未应用某些参数列表的方法。

评估。 类型为 => T 的无参数方法 m 总是通过计算绑定到 m 的表达式而转换为类型 T

因此,类型=> T的正确翻译始终是:

def random$name$here: T

示例

这是一个与之玩耍的示例类:

class TestParamless {
  def paramless: Int = 1
  def callByName(f: => Int) = f
  def example: Int = callByName(paramless)
}

尝试执行 new TestParamless().example 以及在 Scala REPL 中执行 :javap TestParamless


1
@Freewind,是的,没错。 - Gabriele Petronella
为什么 hello(() => "Freewind") 无法编译?似乎 => String 的类型与 () => String 不同。 - Freewind
() => T 是从 Unit 到 T 的函数,而 => T 是按名称调用并具有类型 T,对我来说它们看起来不同。 - Ende Neu
1
我添加了一些更详细的内容 - 我稍后会尝试重新修改以使其更清晰。 - Sean Vieira
@SeanVieira,一个关于答案某些点的新问题:http://stackoverflow.com/questions/25473637/why-def-hellotf-t-f-hello-12-is-compilable-but-def-hellof - Freewind
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接