1

I've been working with Scala for a while and it still troubles me a lot. I don't know why they made it so complex. I am trying to understand matching case classes when there are only two members for this case class

def main(args: Array[String]): Unit = {

    case class X(a: String, i: Int)
    def doSome(x: X): Unit = {
      x match {
        case "x" X 1 => print("ahhh") // <---- HERE ! 
        case X(_, _) => println("")
      }
    }

    doSome(X("x", 1))


    case class Y(a: String, i: Int, j: Int)

    def doAnother(y:Y): Unit = {
      y match {
        case "y" X 1 => print("ahhh") // how to make similar syntax when there are more than one syntax ?
        case Y(_, _,_) => println("")  // this is understandable
      }
    }
    doAnother(Y("y", 1,2))
  }

How can the syntax "x" X 1 match X("x",1) and if "x" X 1 can match match X("x",1) then what matches Y("y",1,2), obviously "y" Y 1 Y 2 doesn't work?

What is so special about the first argument if we can match on "y" Y (1,2)?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Adelin
  • 18,144
  • 26
  • 115
  • 175
  • `case "y" Y (1,2) => ...` – jwvh Jul 09 '20 at 07:24
  • @jwvh You can use more than 2 parameters with infix operators? How does the compiler know you want to give it 3 arguments and that you don't mean to give it a tuple? (`Y("y", (1, 2))`) – user Jul 09 '20 at 13:31
  • 1
    Does this answer your question? [Scala match decomposition on infix operator](https://stackoverflow.com/questions/1022218/scala-match-decomposition-on-infix-operator) – Suma Jul 09 '20 at 16:07

4 Answers4

5

At least in case of List it feels more natural to me, for example consider

List(42, 11) match {
  case head :: tail =>
  case Nil =>
  
}

as opposed to

List(42, 11) match {
  case ::(head, tail) =>
  case Nil =>
  
}

where head :: tail communicates directly the shape of the List.

As a side note, infix notation can sometimes communicate intent more clearly, for example, consider syntax of generalised constraints

implicitly[List[Int] <:< Iterable[Int]]    // infix type notation seems more natural
implicitly[<:<[List[Int], Iterable[Int]]]
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
3

You don't have to use a language feature just because it is there.

In this case I can see no reason not to use the standard class matching version:

x match {
    case X("x", 1) => print("ahhh")
    case _ => println("")
  }
}

y match {
  case Y("y", 1, _) => print("ahhh")
  case _ => println("")
}
Tim
  • 26,753
  • 2
  • 16
  • 29
  • 1
    Well, I don't. But, tones of people are doing it and it is confusion, that is why I want to understand it. And what feature actually are you referring to? – Adelin Jul 09 '20 at 07:43
  • 1
    I am referring to the `"x" X 1` syntax as synonym for `X("x", 1)`. I don't think I have ever seen that on StackOverflow before, and was not even aware it was an option. – Tim Jul 09 '20 at 08:40
  • 1
    Looking at the other answers I realise that I see it all the time but I simply didn't recognise it (e.g. in `case head :: tail =>`) which is really interesting. But I would still say it is a feature best avoided unless there is a strong use case. – Tim Jul 09 '20 at 09:33
2

Ok, so the thing I was looking for is called "Infix Types". From Scala for Impatient, 2nd edition

An infix type is a type with two type parameters, written in “infix” syntax, with the type name between the type parameters. For example, you can write String Map Int instead of Map[String, Int] The infix notation is common in mathematics. For example, A × B = { (a, b) | a Œ A, b Œ B } is the set of pairs with components of types A and B. In Scala, this type is written as (A, B). If you prefer the mathematical notation, you can define type ×[A, B] = (A, B) Then you can write String × Int instead of (String, Int). All infix type operators have the same precedence. As with regular operators, they are left-associative unless their names end in :. For example,
String × Int × Int means ((String, Int), Int). This type is similar to, but not the same, as (String, Int, Int), which could not be written in infix form in Scala.

pedrofurla
  • 12,763
  • 1
  • 38
  • 49
Adelin
  • 18,144
  • 26
  • 115
  • 175
1

To answer your query on What is so special about the first argument if we can match on "y" Y (1,2)?: this is because of how your case class gets decomposed via its unapply method.

The preferred way of matching against a case class is how you've done in the second statement of both your methods.

However, for Y, the preferred way to match would be case Y("y", 1, 2) as mentioned in Tim's comment.

For X, a few ways you can use power of pattern matching are (similarly for Y):

case X("x", 1) => ???
case X(a, 1) => ???
case X(_, 1) => ???
case X("x", _) => ???
case x@X("x", _) => 
case X(a, b) if b > 5 => ???

The following, however, is a very bad style as it compromises readability and hence maintainability of the code

case "x" X 1 => print("ahhh")

As Mario mentioned, the way you're using pattern matching is more suited for lists instead of case classes, as it makes your code consistent with the way a list is structured (head cons tail), and thus enhances readability.

You can go through following articles for a more deeper understanding on how to exploit the power of scala's pattern matching:

Praphull
  • 21
  • 3
  • 1
    Yes I agree but it is used by Scala Parser library like ^^ {case a~b => .... while ~ is a case class with two types. – Adelin Jul 09 '20 at 08:08