为什么Scala在REPL中不允许使用List.map _和类型签名?

4

背景

我最近参加了一个Scala初学者聚会,我们正在讨论方法和函数之间的差异(这个问题也在这里深入探讨)。

例如:

scala> val one = 1
one: Int = 1

scala> val addOne = (x: Int) => x + 1
addOne: Int => Int = <function1>

这表明vals不仅可以是整数类型,还可以是函数类型。我们可以在Scala REPL中看到类型:
scala> :type addOne
Int => Int

我们也可以在对象和类中定义方法:
注:方法是指对象或类中的函数。
scala> object Foo {
 |   def timesTwo(op: Int) = op * 2
 | }
defined module Foo

虽然方法本身没有类型(而是有一个类型签名),但我们可以将其提升为函数来查看它的类型:

scala> :type Foo.timesTwo
<console>:9: error: missing arguments for method timesTwo in object Foo;
follow this method with `_' if you want to treat it as a partially applied function
          Foo.timesTwo
              ^


scala> :type Foo.timesTwo _
Int => Int

目前为止,一切都很顺利。我们甚至谈论了函数实际上是具有apply方法的对象,并且如何去除语法糖以展示这一点:

scala> Foo.timesTwo _ apply(4)
res0: Int = 8

scala> addOne.apply(3)
res1: Int = 4

对我而言,这对于学习语言非常有帮助,因为我可以内化语法实际上所暗示的含义。

问题例子

然而,我们遇到了一个无法识别的情况。例如,考虑一个字符串列表。我们可以对值进行映射函数,演示基本的Scala集合和函数式编程知识:

scala> List(1,2,3).map(_*4)
res2: List[Int] = List(4, 8, 12)

好的,那么List(1,2,3).map()的类型是什么?我希望我们在repl中也能使用相同的:type技巧:

scala> :type List(1,2,3).map _
<console>:8: error: Cannot construct a collection of type Nothing with elements of type Nothing based on a collection of type List[Int].
          List(1,2,3).map _
                      ^

根据API定义,我知道签名是:
def map[B](f: (A) ⇒ B): List[B]

但是还有一个完整的签名:
def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That

问题

我有两个不太理解的问题:

  • 为什么普通的函数提升技巧在List.map中不起作用?有没有一种方法来去掉语法糖,并演示到底发生了什么?
  • 如果方法不能被提升的原因是由于完整的签名“implicit”,那么具体是怎么回事呢?

最后,有没有一种强大的方式从REPL中检查类型和签名?


关于隐式类型签名的问题在这里有解释:http://stackoverflow.com/a/18533437/125901 - Chris Scott
2个回答

7
你遇到的问题与Scala中函数是单态的,而方法可以是多态的有关。因此,为了创建List.map的函数值,必须知道类型参数BThat。编译器试图推断参数,但无法得出任何明智的结果。如果提供参数,则会得到有效的函数类型:
scala> List(1,2,3).map[Char, List[Char]] _
res0: (Int => Char) => List[Char] = <function1>

scala> :type res0
(Int => Char) => List[Char]

1
没有实际的函数参数,函数的推断类型是 Int => Nothing,但目标集合也是 Nothing。在作用域中没有合适的 CanBuildFrom[List[Int], Nothing, Nothing],我们可以在 REPL 中输入 implicitly[CanBuildFrom[List[Int], Nothing, Nothing]](会出现相同的错误)来查看。如果您提供类型参数,则可以获得一个函数:
scala> :type List(1,2,3).map[Int, List[Int]] _
(Int => Int) => List[Int]

我认为你无法在REPL中查看方法签名。这就是Scaladoc的用途。


我仍然认为在REPL中看到类型签名会很有用。这不仅比检查scaladoc更容易,而且还允许您检查在运行时定义或组合的代码。是否有一种方法可以在不转到java.lang.reflect的情况下实现这一点? - Chris Scott
1
你可能会在scala.reflect中更加幸运,但即使如此,它也不会告诉你各种参数的含义或方法的作用。REPL可以改进以显示scaladoc注释,但这仍然是在浏览器中查看scaladoc的劣质替代品。文档是Scala发行版的一部分,所以只需找到硬盘上的文件夹,并在浏览器选项卡中永久打开它们即可。 - Luigi Plinge
2
@16bytes 其实我发现你可以在REPL中通过在键入方法名称后使用Tab键来查看方法签名。它似乎只在你有一个单一标识符跟着点和方法名称时才有效,所以如果你定义了val list = List(1,2,3),那么list.map接着按Tab键就会显示其签名,但是List(1,2,3).map接着按Tab键会显示一个错误信息。不过这也许有点用处。 - Luigi Plinge

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