Enum pattern matching


#1

Hello,
I recently got to use the when expression with an enum class and I think it’s kind of… unneccessary.
To explain my situation: I programmed a game called 2048 and for those who don’t know the game,
you have for moves (left, right, up, down) and each moves the tiles of the game in the given direction.

That led me to the decision to write this stuff:


enum class Move {
    LEFT, RIGHT, UP, DOWN
}

// Somewhere else:
fun push(move: Move) =
    when(move) {
        LEFT -> pushLeft()
        RIGHT -> pushRight()
        ...
    }

fun pushLeft() {
   ...
}

I feel like the when expression is not really needed and can be avoided. It would be nicer, If I could directly say something like this:

fun push(Move.LEFT) {
    ...
}

fun push(Move.RIGHT) {
    ...
}

#2
fun main(args: Array<String>) {
   move(Move.LEFT)
}

sealed class Move{
    object LEFT:Move()
    object RIGHT:Move()
}

fun move(m:Move.LEFT)=println("left")

fun move(m:Move.RIGHT)=println("right")

#3

Hm ok, didn’t know about this. Looks fine to me, thanks


#4

The solution with sealed class given above is indeed ingenious, but it works only when the move type is known statically, e.g this will not work:

fun main(args: Array<String>) {
    val m: Move = Move.LEFT
    move(m) // ERROR: None of the following function can be called with supplied arguments
}

#5

You spoiled its surprise.


#6

Why not add a general function that forwards to an overloaded function on the different objects which calls the correct implementation.

abstract sealed class Move {
    object LEFT:Move() {
      fun move() = move(LEFT)
    }
   // The other cases
   abstract fun move():Unit
}

inline fun move(m:Move) = m.move()

Of course this is a bit more code, but allows the unspecified code as well. Alternatively you could do something with invokedynamic to implement parameter based dispatch :wink: (oops, that’s not supported in Java 6).


#7

Enums can have methods that each individual case of the enum can override.
https://kotlinlang.org/docs/reference/enum-classes.html#anonymous-classes


#8

Here is another way to do what you want. This way you never have to touch the enum when you want to attach new behavior to its values. You can leave of .invoke if you like. And you could also make moveInDirection(...) an extension function of Direction:

package enumpolymorphism

import enumpolymorphism.Direction.EAST
import enumpolymorphism.Direction.NORTH
import enumpolymorphism.Direction.SOUTH
import enumpolymorphism.Direction.UP
import enumpolymorphism.Direction.WEST

enum class Direction {
    NORTH, EAST, SOUTH, WEST, UP
}

fun main(arguments: Array<String>) {
    moveInDirection(NORTH)
    moveInDirection(WEST)
    moveInDirection(UP)
}

val directionAction = mapOf(
        NORTH to ::moveNorth,
        EAST to ::movEast,
        SOUTH to ::moveSouth,
        WEST to ::moveWest
)

private fun moveInDirection(direction: Direction) {
    (directionAction[direction] ?: throw IllegalStateException("Unknown direction: $direction")).invoke()
}

fun moveNorth() {
    println("Moving NORTH")
}

fun movEast() {
    println("I am heading EAST")
}

fun moveSouth() {
    println("Off to the SOUTH")
}

fun moveWest() {
    println("Go WEST")
}

And here is some example output:

Moving NORTH
Go WEST
Exception in thread "main" java.lang.IllegalStateException: Unknown direction: UP
    at enumpolymorphism.EnumPolymorphismKt.moveInDirection(EnumPolymorphism.kt:27)
    at enumpolymorphism.EnumPolymorphismKt.main(EnumPolymorphism.kt:16)

#9

This approach certainly works, but is not typesafe. If you really want to achieve the independence from enum what you can do is use a visitor pattern. The visitor would be called with a different method for each enum value (adding a value would require updating the visitor interface/class) and the compiler will enforce completeness (no new enum without compiler errors until you adapt).

enum Direction {
  interface Visitor {
    fun visitNorth()
    fun visitWest(
    fun visitSouth()
    fun visitEast()
  }

  WEST {
    fun visit(visitor:Visitor) { visitor.visitWest() }
  }
  // all the other directions

  abstract fun visit(visitor:Visitor)
}

//Usage:
fun printDirection(direction: Direction) =
    direction.visit(object:Direction.Visitor {
        fun visitWest() = System.out.println("West")
        fun visitNorth() = System.out.println("North")
        fun visitEast() = System.out.println("East")
        fun visitSouth() = System.out.println("South")
    })
}

If you wanted to have visitors with return types you can do that with generics. You can probably make it more elegant with better names. The current solution will create (from a JVM perspective) a new anonymous inner class on every invocation (unless the optimizer is smart). If you don’t want to handle (non-dynamic dispatch) parameters you can make the object a constant (outside the function body) and not pay that cost.


#10

Just use the when() it is better because your enums are plain enums and the when is a cocise way to couple the enums to your functions.


#11

SealedClassSolution: How disappointing…

The other solutions: I would like to have my methods for the moves outside of the enum/class. There is something representing the possible Moves and there is something representing the game and the game knows how a certain move is going to affect itself - not the other way around and because of that, I can’t give the enums the method.

The visitor pattern: That would work, but yes, I would need the performance, I cannot pay the cost and it seems more complicated than a simple when.

Nevertheless, thanks for the solutions so far. It shows me that there are still many possibilities I can discover in kotlin.


#12

@seventhone there is a direct way to do what you wish with clean enums:

enum class Move {
    LEFT {
        override fun push() {}
    }, 

    RIGHT{
        override fun push() {}
    }, 

    UP{
        override fun push() {}
    }, 

    DOWN{
        override fun push() {}
    };

    abstract fun push()
} 

//USAGE:
val m: Move = ...
m.push()

#13

Of course this is the straightforward solution. It does however require the function to be defined on the enum. That is where the visitor approach allows for typesafe complete implementation independent from the enum. When statements are more straightforward but require completeness (and some static analysis to make sure they are)


#14

The sealed enum solution would be nice if we could define a function like

fun move(m:Move) switch;

which would delegate to one of

fun move(m:Move.LEFT)=println("left")
...

My funny syntax misuses the switch keyword in place of the body, which can be trivially autogenerated.


#15

I’m not sure what the language goals are, but what you are asking for is dynamic dispatch in a roundabout way. Some keyword or annotation would do this closer to the language

fun move(@dispatch m: Move.LEFT)

This dispatch would then imply a synthetic function and/or invokedynamic, but the implementation would be left to the runtime.