【问题标题】:Why Scala doesn't allow List.map _ and type signatures in REPL为什么 Scala 不允许 List.map _ 和 REPL 中的类型签名
【发布时间】:2013-09-11 21:27:20
【问题描述】:

背景

我最近参加了一个初学者 Scala 聚会,我们正在讨论方法和函数之间的区别(也深入讨论了here)。

例如:

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?有没有办法对错误陈述进行去语法糖化以证明发生了什么?
  • 如果无法解除方法的原因是由于完整的签名“隐式”,那到底发生了什么?

最后,是否有一种可靠的方法来检查 REPL 中的类型和签名?

【问题讨论】:

标签: scala


【解决方案1】:

您遇到的问题与以下事实有关:在 Scala 中,functions are monomorphic, while methods can be polymorphic。因此,必须知道类型参数BThat 才能为List.map 创建函数值。

编译器试图推断参数,但无法提出任何合理的建议。如果你提供参数,你会得到一个有效的函数类型:

scala> List(1,2,3).map[Char, List[Char]] _
res0: (Int => Char) => List[Char] = <function1>

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

【讨论】:

    【解决方案2】:

    没有实际的函数参数,函数的推断类型是Int =&gt; 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 做到这一点?
    • scala.reflect 可能会让您更幸运,但即便如此,它也不会告诉您各种参数的用途或方法的作用。可以改进 REPL 以显示 scaladoc cmets,但这仍然是在浏览器中查看 scaladoc 的可怜表亲。这些文档是 Scala 发行版的一部分,因此只需在硬盘驱动器上找到该文件夹​​并在浏览器选项卡中永久打开它们即可。
    • @16bytes 实际上我发现您可以在输入方法名称后使用 Tab 键在 REPL 中看到方法签名。它似乎只在您有一个标识符后跟点和方法名称时才有效,因此如果您定义 val list = List(1,2,3)list.map 后跟 Tab 会显示其签名,但 List(1,2,3).map 后跟 Tab 会给出错误消息。不过,可能会有一些用处。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-03
    • 1970-01-01
    • 2011-02-02
    相关资源
    最近更新 更多