【问题标题】:Obtaining array element in Scala works only with explicit apply method在 Scala 中获取数组元素仅适用于显式应用方法
【发布时间】:2017-03-26 00:42:21
【问题描述】:

在 Scala 中访问集合元素是通过apply 方法完成的。话虽如此,我尝试从标准输入中读取数字并在一行中将第二个数字作为 int 获取。

  def main(args: Array[String]): Unit = {
    val number = StdIn.readLine().split(" ").map(_.toInt)(1)
  }

IntelliJ 标记此1 并显示错误(编译尝试后显示相同):

错误:(11, 60) 类型不匹配;找到:需要 Int(1): scala.collection.generic.CanBuildFrom[Array[String],Int,?] val number = StdIn.readLine().split(" ").map(_.toInt)(1)

折叠括号中的表达式也无济于事。但是,当我将映射输入拆分为 int 数组并将元素获取到其他行时,一切正常。

  def main(args: Array[String]): Unit = {
    val numbers = StdIn.readLine().split(" ").map(_.toInt)
    numbers(1)
  }

apply 的显式调用也可以完成这项工作:

val number = StdIn.readLine().split(" ").map(_.toInt).apply(1)

为什么会发生这种奇怪的行为?通过array(5) 获取元素只是array.apply(5) 的捷径吧?

【问题讨论】:

    标签: arrays scala


    【解决方案1】:

    正如编译器已经指出的那样,map 是用两个 arg 列表定义的。第二个包含一个隐式参数:implicit bf: CanBuildFrom[Repr, B, That]

    对于绝大多数情况,用户不必担心指定第二个参数。具体来说,在您的情况下,这是编译器“弄清楚”的:

    StdIn.readLine().split(" ").map(_.toInt)(Array.canBuildFrom[Int])
    

    所以,如果不想写出apply,那么以下是唯一的选择:

    StdIn.readLine().split(" ").map(_.toInt)(Array.canBuildFrom[Int])(1)
    

    添加一组括号仍然无法解决问题,这有点违反直觉:

    ( StdIn.readLine().split(" ").map(_.toInt) )(1)  //does not compile, same error
    

    也许最好用一个更简单的例子来演示一下。考虑以下函数:

    def add(x:Int)(y:Int) = x + y
    

    add(2) 的类型是Int => Int(因为我们没有指定y)。请注意,在其周围添加一组括号不会更改返回类型,即(add(2)) 仍然具有Int => Int 类型。同样,(Array("1").map(_.toInt))(1) 在使用 apply 的简写之前仍然需要 CanBuildFrom 的实例。


    关于 Scala 集合的推荐阅读:

    和隐式作用域:

    【讨论】:

    • 神秘的事情(至少对我而言)是为什么val number = (StdIn.readLine().split(" ").map(_.toInt))(1) 不起作用?对隐式参数的搜索扩展到括号​​之外,就好像它们不存在一样。我在 Scala 规范中找不到解释。
    • @JoePallas 更新了我的答案,希望能消除困惑。
    • 更新的答案打开了一罐蠕虫,因为我不确定我们是否可以在不进行显式 eta-expansion add(2) _ 的情况下谈论 add(2) 的类型。看起来这就是部分混淆的来源:隐式解析发生在 eta-expansion (stackoverflow.com/a/18635935/3851755) 之前。有趣的是,这导致了另一种解决方案:(StdIn.readLine().split(" ").map(_.toInt) _)(1)
    • @JoePallas 当然,add(2) 本身不是一个有效的表达式,例如val x = add(2) 不会编译。但是,我指的是add(2) 作为更大表达的一小部分。我要说明的一点是add(2)(3)(add(2))(3) 相同,与((add(2)))(3) 相同,依此类推...第二个参数列表必须在之前使用简写为apply 因为apply 的调用是在表达式的结果 上完成的。即使用速记符号调用apply 与第二个参数列表的显式规范冲突...
    【解决方案2】:

    您与map 的隐式参数发生冲突。这是应用需要显式调用的情况之一。

    这里有一些复制错误的 REPL:

    scala> class Foo {
      def apply() { println("bar") }
    }
    
    scala> def makeFoo(): Foo = new Foo
    
    scala> makeFoo()()
    bar // got the println from the apply here
    
    scala> def makeFooImplicit()(implicit x: Int): Foo = new Foo
    
    scala> implicit val x = 5
    
    scala> makeFooImplicit()()
      <console>:11: error: not enough arguments for method makeFooImplicit:(implicit x: Int)Foo.
      Unspecified value parameter x.
              makeFooImplicit()()
    

    【讨论】:

    • 那么如何调用makeFooImplicit
    • @BartłomiejSzałach makeFooImplicit()makeFooImplicit()(1) 都将返回 Foo 的实例。前者有效,因为定义了implicit val x = 5,这实际上意味着makeFooImplicit()(5)
    • 您可以说val f = makeFooImplicit(); f() 并获取打印输出。
    猜你喜欢
    • 1970-01-01
    • 2014-11-06
    • 2011-12-08
    • 1970-01-01
    • 1970-01-01
    • 2013-09-11
    • 2019-04-12
    • 2022-12-04
    • 1970-01-01
    相关资源
    最近更新 更多