HiveBrain v1.2.0
Get Started
← Back to all entries
patternkotlinMinor

Kotlin Data Class Wildcard

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dataclasskotlinwildcard

Problem

I'm writing a small custom DSL and interpreter as an exercise in understanding how the language stack (lexer/parser/(interpreter/compiler)) works. Because I want to be able to report the location of semantic errors, the AST needs to retain information about where the elements were in the source.

For this purpose I wrote a data class Point(val line: Int, val column: Int). All well and good, until I realized that manually specifying the position of EVERY node in the AST for unit tests on parsing would be prohibitively time consuming (and error prone). (Obviously, some tests need to do so to test the assigning of positions, but some is a lot fewer than all.)

To allow me to write more good unit tests quicker, I decided I wanted to default the position of all my nodes to a wildcard value that would match all valid Points. The parsed nodes would have the correct position, but the manually assembled AST to check against would not specify (and thus not test) the positions. This is the solution I came up with.

/**
 * DO NOT EXTEND THIS CLASS!!
 *
 * This is an custom open data class to allow a wildcard value.
 */
open class Point(val line: Int, val column: Int) {
    object Wildcard : Point(-1, -1) {
        override fun toString() = "Point.Wildcard"
        override fun copy() = this
    }

    override fun equals(other: Any?) = when {
        this === other         -> true
        other === Wildcard     -> true
        other !is Point        -> false
        this === Wildcard      -> true
        line != other.line     -> false
        column != other.column -> false
        else                   -> true
    }

    override fun hashCode(): Int { // generated
        var result = line
        result = 31 * result + column
        return result
    }

    override fun toString() = "Point(line=$line, column=$column)"
    operator fun component1() = line
    operator fun component2() = column
    open fun copy() = Point(line, column)
}


I wanted to keep all the

Solution

Your equals method is not transitive (e.g. Point(2, 2) == Point.Wildcard and Point.Wildcard == Point(3, 3) but Point(2, 2) != Point(3, 3). See Any.equals - stdlib - Kotlin Programming Language for more details on the requirements for equals. You might also consider using EqualsTester from guava-testlib for testing your own implementations of equals.

If you define your own method instead of defining a custom, non-transitive equals implementation than your existing solution can become much simpler:

data class Point(val line: Int, val column: Int) {
    companion object {
        val wildcard = Point(-1, -1)
    }

    infix fun matches(other: Point): Boolean {
        return this == other || other === wildcard || this === wildcard
    }
}


Notes:

  • Point(2, 2) matches Point(-1, -1) will return false.



  • Point(2, 2) matches Point.wildcard will return true.



  • If you want both to return true then replace the === with == in matches.



  • Marking matches as infix is purely optional but it seems to me like a good fit.

Code Snippets

data class Point(val line: Int, val column: Int) {
    companion object {
        val wildcard = Point(-1, -1)
    }

    infix fun matches(other: Point): Boolean {
        return this == other || other === wildcard || this === wildcard
    }
}

Context

StackExchange Code Review Q#152403, answer score: 2

Revisions (0)

No revisions yet.