Konstantin Milyutin
Konstantin Milyutin

Reputation: 12356

Curried update method

I'm trying to have curried apply and update methods like this:

def apply(i: Int)(j: Int) = matrix(i)(j)
def update(i: Int, j: Int, value: Int) = 
  new Matrix(n, m, (x, y) => if ((i,j) == (x,y)) value else matrix(x)(y))

Apply method works correctly, but update method complains:

scala> matrix(2)(1) = 1
<console>:16: error: missing arguments for method apply in class Matrix;
follow this method with `_' if you want to treat it as a partially applied function
              matrix(2)(1) = 1

Calling directly update(2)(1)(1) works, so it is a conversion to update method that doesn't work properly. Where is my mistake?

Upvotes: 2

Views: 881

Answers (2)

Miles Sabin
Miles Sabin

Reputation: 23046

The desugaring of assignment syntax into invocations of update maps the concatenation of a single argument list on the LHS of the assignment with the value on the RHS of the assignment to the first parameter block of the update method definition, irrespective of how many other parameter blocks the update method definition has. Whilst this transformation in a sense splits a single parameter block into two (one on the LHS, one on the RHS of the assignment), it will not further split the left parameter block in the way that you want.

I also think you're mistaken about the example of the explicit invocation of update that you show. This doesn't compile with the definition of update that you've given,

scala> class Matrix { def update(i: Int, j: Int, value: Int) = (i, j, value) }
defined class Matrix

scala> val m = new Matrix
m: Matrix = Matrix@37176bc4

scala> m.update(1)(2)(3)
<console>:10: error: not enough arguments for method update: (i: Int, j: Int, value: Int)(Int, Int, Int).
Unspecified value parameters j, value.
              m.update(1)(2)(3)
                      ^

I suspect that during your experimentation you actually defined update like so,

scala> class Matrix { def update(i: Int)(j: Int)(value: Int) = (i, j, value) }
defined class Matrix

The update desugaring does apply to this definition, but probably not in the way that you expect: as described above, it only applies to the first argument list, which leads to constructs like,

scala> val m = new Matrix
m: Matrix = Matrix@39741f43

scala> (m() = 1)(2)(3)
res0: (Int, Int, Int) = (1,2,3)

Here the initial one-place parameter block is split to an empty parameter block on the LHS of the assignment (ie. the ()) and a one argument parameter block on the RHS (ie. the 1). The remainder of the parameter blocks from the original definition then follow.

If you're surprised by this behaviour you won't be the first.

The syntax you're after is achievable via a slightly different route,

scala> class Matrix {
     |   class MatrixAux(i : Int) {
     |     def apply(j : Int) = 23
     |     def update(j: Int, value: Int) = (i, j, value)
     |   }
     | 
     |   def apply(i: Int) = new MatrixAux(i)
     | }
defined class Matrix

scala> val m = new Matrix
m: Matrix = Matrix@3af30087

scala> m(1)(2)      // invokes MatrixAux.apply
res0: Int = 23

scala> m(1)(2) = 3  // invokes MatrixAux.update
res1: (Int, Int, Int) = (1,2,3)

Upvotes: 7

Malte Schwerhoff
Malte Schwerhoff

Reputation: 12852

My guess is, that it is simply not supported. Probably not due to an explicit design decision, because I don't see why it shouldn't work in principle.

The translation concerned with apply, i.e., the one performed when converting m(i)(j) into m.apply(i, j) seems to be able to cope with currying. Run scala -print on your program to see the code resulting from the translation.

The translation concerned with update, on the other hand, doesn't seem to be able to cope with currying. Since the error message is missing arguments for method apply, it even looks as if the currying confuses the translator such that it tries to translate m(i)(j) = v into m.apply, but then screws up the number of required arguments. scala -print unfortunately won't help here, because the type checker terminates the translation too early.

Here is what the language specs (Scala 2.9, "6.15 Assignments") say about assignments. Since currying is not mentioned, I assume that it is not explicitly supported. I couldn't find the corresponding paragraph for apply, but I guess it is purely coincidental that currying works there.

An assignment f(args) = e with a function application to the left of the ‘=’ operator is interpreted as f.update(args, e), i.e. the invocation of an update function defined by f.

Upvotes: 0

Related Questions