Julian A Avar C
Julian A Avar C

Reputation: 878

Argument destructuring

I'm coming from a JS background. In JS I could write something as follows:

let x = [[1, 0], [2, 1], [3, 2], [4, 3]]
x.forEach(([n, i]) => console.log())

So I was trying to convert to Scala, and I found a bunch of ways to do it. But I don't understand how the match and case statement are disappearing.

val x = Array(1, 2, 3, 4).zipWithIndex

// does what I expect
x.foreach((a) => {
    println(a)
})

// uses match
x.foreach((a) => {
    a match {
        case (n, i) => println(s"$n $i")
    }
})

// gets rid of redundant variable name
x.foreach({
    _ match {
        case (n, i) => println(s"$n $i")
    }
})

// gets rid of unnecesary scope
x.foreach(_ match {
    case (n, i) => println(s"$n $i")
})

Up to here, it makes sense. The below code I found online when looking how to loop with index.

// where did `match` go?
x.foreach({
    case (n, i) => println(s"$n $i")
})

// and now `case` is gone too?
x.foreach((n, i) => println(s"$n $i"))

What is going on here? I would call it destructuring coming from JS, but this seems like a hidden/implicit match/case statement. Are there rules around that? How do I know if there should be an implicit match/case statement?

Upvotes: 1

Views: 1042

Answers (2)

Jörg W Mittag
Jörg W Mittag

Reputation: 369536

// where did `match` go?
x.foreach({
    case (n, i) => println(s"$n $i")
})

This is a Pattern Matching Anonymous Function, also sometimes called a Partial Function Literal. See Scala Language Specification 8.5 Pattern Matching Anonymous Functions for all the gory details. Simply put, the expression

{
  case p1 => e1
  case p2 => e2
  // …
  case pn => en
}

is equivalent to

(x1: S1, x2: S2, /* … */, xn: Sn) => (x1, x2, /* … */, xn) match {
  case p1 => e1
  case p2 => e2
  // …
  case pn => en
}

provided that the result type is SAM-convertible to FunctionN[S1, S2, /* … */, Sn, R], or as a special case PartialFunction1[S1, R] (which is where the name Partial Function Literal comes from.)

// and now `case` is gone too?
x.foreach((n, i) => println(s"$n $i"))

This is a new feature of Scala 3. For a very long time, the Scala Designers wanted to unify Tuples and Argument Lists. In other words, they wanted to make it so that methods in Scala only ever take one argument, and that argument is a tuple. Unfortunately, it turned out that a) this massively breaks backwards-compatibility and b) massively breaks platform interoperability.

Now, Scala 3 was an opportunity to ignore problem a), but you cannot ignore problem b) since one of the major design goals of Scala is to have seamless, tight, good, performant integration with the underlying host platform (e.g. .NET in the case of the now-abandoned Scala.NET, the ECMASCript / HTML5 / DOM / WebAPI platform in the case of Scala.js, the native Operating System in the case of Scala-native, or the Java platform (JRE, JDK, JVM, J2SE, J2EE, Java, Kotlin, Clojure, etc.) in the case of Scala-JVM).

However, the Scala designers managed to find a compromise, where arguments and tuples are not the same thing, but parameters can be easily converted to tuples and tuples can be easily converted to arguments.

This is called Parameter Untupling, and it basically means that a function of type FunctionN[S1, S2, /* … */, Sn, R] can be automatically converted to a function of type Function1[(S1, S2, /* … */, Sn), R] which is syntactic sugar for Function1[TupleN[S1, S2, /* … */, Sn], R].

Simply put,

(p1: S1, p2: S2, /* … */, pn: Sn) => e: R

can automatically be converted to

(x: (S1, S2, /* … */, Sn)) => {
  val p1: S1 = x._1
  val p2: S2 = x._2
  // …
  val pn: Sn = x._n

  e
}

Note: unfortunately, there is no comprehensive specification of Scala 3 yet. There is a partial Language Reference, which however only describes differences to Scala 2. So, you typically have to bounce back and forth between the SLS and the Scala 3 docs.

Upvotes: 5

Max Naumenko
Max Naumenko

Reputation: 69

About match keyword:

// where did `match` go?
x.foreach({
    case (n, i) => println(s"$n $i")
})

This is the language specification, you can ignore match keyword in anonymous functions: https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#pattern-matching-anonymous-functions

Upvotes: 3

Related Questions