3

Accessing collection elements in Scala is done by apply method. Having said that I tried to read numbers from standard input and get the second one as int in just one line.

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

IntelliJ marks this 1 and show the error (the same is shown after the compilation attempt):

Error:(11, 60) type mismatch; found : Int(1) required: scala.collection.generic.CanBuildFrom[Array[String],Int,?] val number = StdIn.readLine().split(" ").map(_.toInt)(1)

Folding the expression in the parentheses dosn't help as well. However, when I split mapping input to int array and getting the element to other lines, everything works fine.

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

The explicit invocation of apply also does the job:

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

Why does this weird behaviour happens? Obtaining element via array(5) is just a shortcut for array.apply(5), isn't it?

Bartłomiej Szałach
  • 2,393
  • 3
  • 30
  • 50

2 Answers2

3

As compiler already pointed out, map is defined with two arg lists. second one containing a single implicit param: implicit bf: CanBuildFrom[Repr, B, That]

For a vast majority of scenarios, the users don't worry about specifying the second arg. Specifically, in your case this is what the compiler "figures out":

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

So, if writing out apply is not desired then the following is the only choice:

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

It's a bit counter-intuitive that adding a set of parentheses still doesn't quite do the trick:

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

Perhaps it's best to demonstrate what's going with a simpler example. Consider the following function:

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

The type of add(2) is Int => Int (since we haven't specified y). Note that adding a set of parentheses around it doesn't change the return type, i.e. (add(2)) still has a type Int => Int. Similarly, (Array("1").map(_.toInt))(1) still requires an instance of CanBuildFrom before using shorthand for apply.


Suggested reading about Scala collections:

and implicit scope:

Community
  • 1
  • 1
Andrey
  • 8,882
  • 10
  • 58
  • 82
  • The thing that's mysterious (at least to me) is why does `val number = (StdIn.readLine().split(" ").map(_.toInt))(1)` not work? The search for the implicit parameter extends outside the parentheses as if they are not there. I can't find an explanation in the Scala spec. – Joe Pallas Nov 13 '16 at 01:54
  • @JoePallas updated my answer, hopefully that clears up the confusion. – Andrey Nov 13 '16 at 03:01
  • the updated answer opens a can of worms because I'm not sure we can talk about the type of `add(2)` without doing the explicit eta-expansion `add(2) _`. And it looks like that's where part of the confusion comes from: implicit resolution occurs before eta-expansion (http://stackoverflow.com/a/18635935/3851755). Interestingly, that leads to an alternative solution: `(StdIn.readLine().split(" ").map(_.toInt) _)(1)` – Joe Pallas Nov 13 '16 at 18:44
  • @JoePallas sure, on its own `add(2)` is not a valid expression, e.g. `val x = add(2)` will not compile. however, i'm referring to `add(2)` as a smaller part of bigger expression. the point i'm trying to make is `add(2)(3)` is the same as `(add(2))(3)` is the same as `((add(2)))(3)` and so on... the second arg list must be known *before* using shorthand for `apply` since the invocation of `apply` is done on the *result of the expression*. i.e. invoking `apply` using shorthand notation clashes with explicit specification of the second arg list... – Andrey Nov 13 '16 at 19:28
1

You're getting a collision with an implicit argument to map. This is one of the cases apply needs to be invoked explicitly.

Here's some REPL that replicates the error:

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()()
Tim
  • 3,675
  • 12
  • 25