Search code examples
scalacompiler-errorspattern-matchingscala-compiler

Pattern matching - value is not a member on a bound variable


I am working my way through Scala (ver. 2.13.2), and here I've defined a simple linked list with a trait ListSeq. Also, I wanted to override a toString method for pretty-printing. For this, I decided to use pattern-matching. The desired result can be seen in assert cases

sealed trait ListSeq {
    override def toString: String = s"[$elemSequence]"
    private def elemSequence: String = {
        this match {
            case ListPair(hd, tl @ ListPair(_, _)) => s"$hd, ${tl.elemSequence}"
            case ListPair(hd, EmptyList) => s"$hd"
            case EmptyList => ""
        }
    }
}
case class ListPair(head: Int, tail: ListSeq) extends ListSeq
case object EmptyList extends ListSeq

object ListSeqExample extends App {
    val seq1 = ListPair(1, ListPair(2, ListPair(3, EmptyList)))
    val seq2 = EmptyList
    
    assert(seq1.toString == "[1, 2, 3]")
    assert(seq2.toString == "[]")
}

The problem

This code doesn't compile, the error is:

value elemSequence is not a member of ListPair
            case ListPair(hd, tl @ ListPair(_, _)) => s"$hd, ${tl.elemSequence}"

It is not clear to me why this error appears. From what I know, Scala can match on nested fields and bind the fields to a variable - like what is done in case ListPair(hd, tl @ ListPair(_, _)). But from the error message, it seems that it cannot guess the type of bound object (ListPair)

Weird behavior

Another interesting thing is - if I redefine the ListSeq in the following way - by removing the elemSequence method, and all string creation is done in toString - there is no error:

sealed trait ListSeq {
    override def toString: String = this match {
        case ListPair(hd, tl @ ListPair(_, _)) => s"$hd, ${tl.toString}"
        case ListPair(hd, EmptyList) => s"$hd"
        case EmptyList => ""
    }
}

I know that the result of toString will be slightly different (no braces), but it is not the main point here.

The question

Why is there an error in one case, while it happily compiles in another?


Solution

  • It returns an error because elemSequence is a private method defined in ListSeq so it is not visible in ListPair.

    One solution could be to change the visibility to protected (and add the modifier final so the child classes cannot override the method):

    sealed trait ListSeq {
        override def toString: String = s"[$elemSequence]"
        final protected def elemSequence: String = {
            this match {
                case ListPair(hd, tl @ ListPair(_, _)) => s"$hd, ${tl.elemSequence}"
                case ListPair(hd, EmptyList) => s"$hd"
                case EmptyList => ""
            }
        }
    }
    case class ListPair(head: Int, tail: ListSeq) extends ListSeq
    case object EmptyList extends ListSeq
    
    object ListSeqExample extends App {
        val seq1 = ListPair(1, ListPair(2, ListPair(3, EmptyList)))
        val seq2 = EmptyList
        
        assert(seq1.toString == "[1, 2, 3]")
        assert(seq2.toString == "[]")
    }