Arrays of arrays are not given special treatment; they're just arrays of (something). Thus, there's no special way to access them via tuples. But as you've suggested, you can create such a way.
You can
implicit class ArrayOps2D[@specialized T](xss: Array[Array[T]]) {
def apply(ij: (Int, Int)) = xss(ij._1)(ij._2)
def apply(i: Int, j: Int) = xss(i)(j)
def update(ij: (Int, Int), t: T) { xss(ij._1)(ij._2) = t }
def update(i: Int, j: Int, t: T) { xss(i)(j) = t }
}
You might think of doing
implicit class ArrayOps2D[T](val xss: Array[Array[T]]) extends AnyVal {
def apply(ij: (Int, Int)) = xss(ij._1)(ij._2)
def apply(i: Int, j: Int) = xss(i)(j)
def update(ij: (Int, Int), t: T) { xss(ij._1)(ij._2) = t }
def update(i: Int, j: Int, t: T) { xss(i)(j) = t }
}
but this doesn't work as well in my opinion. Due to implementation limitations, you can't specialize an AnyVal. Furthermore, the former is probably better if you are using primitives a lot since it avoids boxing the primitives (and the JVM can handle avoiding object creation, hopefully), while the latter is more efficient if you have non-primitives most of the time (e.g. strings) since you don't (formally) create an object. But your example uses primitives.
In any case, if you do this you'll have seamless two-index addressing with tuples and pairs of arguments (as I've written it). You cannot use the update methods completely seamlessly, though! They will mostly work, but they won't automatically promote numeric types. So if you have doubles and you write a(1,2) = 3 it will fail, because it doesn't find an update(Int, Int, Int) method, and doesn't think to use the update(Int, Int, Double). But you can fix that yourself by doing the conversion (or in this case writing 3d).