08 July, 2014

Comparison with constants in pattern matching

   Today I faced scala.MatchedError: 0 (of class java.lang.Integer) in overridden renderComponent method of scala.swing.Table, when I tried to add rows.
    val table = new Table(0, 2) {
      override protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int): Component  = {
        col match {
          case 1 => tcr.componentFor(this, sel, foc, "bar", row, col)
        }
      }
    }
It turned out that I need a match for all possible values. In this particular case for 0, because I have two columns. So adding undermentioned code solved the problem
          case 0 => super.rendererComponent(sel, foc, row, col)
At the beginning I was confused by the error message and thought that there might be some casting problem. So I tried to store 1 into value
    val table = new Table(0, 2) {
      override protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int): Component  = {
        val columnIndex: Int = 1
        col match {
          case columnIndex => tcr.componentFor(this, sel, foc, "bar", row, col)
        }
      }
    }
Ta-dah! Program compiles, adding rows to table works, it seemed that I fixed the problem, but in fact I just made one of the most common mistakes that Scala beginners do. It is the second time when I fall into this trap, that is why I chose to mention about it on my blog.

In this situation columnIndex inside match is not the same columnIndex that was defined in first line of the function. It is variable initialized with col value. As there is no condition check just an assignment, the expression to the right of the arrow is always executed and each column contains component that I only wanted to appear in second one.

To fix this issue I need to use stable identifier, which in Scala must either start with uppercase letter
    val table = new Table(0, 2) {
      override protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int): Component  = {
        val ColumnIndex: Int = 1
        col match {
          case ColumnIndex => tcr.componentFor(this, sel, foc, "bar", row, col)
        }
      }
    }
or be surrounded by backticks
    val table = new Table(0, 2) {
      override protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int): Component  = {
        val columnIndex: Int = 1
        col match {
          case `columnIndex` => tcr.componentFor(this, sel, foc, "bar", row, col)
        }
      }
    }
The first solution seems to be much better as it is agreeable with Scala's naming convention, according to which constants names should start with capital letter.

Please notice that you cannot define columnIndex as variable. Changing val to var in last two snippets will produce compile error.

No comments:

Post a Comment