Scala finally clicked. It took a year of writing production code with it and completing Coursera’s Functional Programming Principles in Scala course (lucky me, it’s taught by the creator of Scala, Martin Odersky). When I got to the programming exercises in the Scala chapter of Seven Languages in Seven Weeks, I wanted my code to follow every Scala principle. I wanted to only use immutable variables and use functional programming components instead of for loops.

Here’s my tic tac toe code in its entirety. You can run it using Scala’s REPL. I didn’t want to spend an entire weekend optimizing it, so this is the first complete version. I’ll tell you why it sucks at the later.

The Code

class Player()
case class X() extends Player {
  override def toString(): String = {
    "X"
  }
}
case class O() extends Player {
  override def toString(): String = {
    "O"
  }
}

type Owner = Option[Player]

class Status()
case class Winner(player: Player) extends Status
case class InProgress() extends Status
case class Draw() extends Status

case class Tile(loc: Int, owner: Owner) {
  override def toString(): String = {
    owner match {
      case Some(player) => player.toString
      case _ => s"$loc"
    }
  }
}

val blankState = (0 to 8).map(loc => new Tile(loc, None)).toList

case class Board(state: List[Tile] = blankState, nextPlayer: Player = X()) {
  def move(loc: Int) : Board = {
    val (front, back) = state.splitAt(loc)

    back match {
      case Tile(_, None) :: tail => {
        val newState = front ::: new Tile(loc, Some(nextPlayer)) :: tail
        new Board(newState, otherPlayer)
      }
      case _ => throw new Error(s"Invalid location $loc")
    }
  }

  def otherPlayer = {
    nextPlayer match {
      case X() => O()
      case O() => X()
    }
  }

  // val (x, y) = (v % 3, v / 3)
  // 0,0 | 1,0 | 2,0
  // 0,1 | 1,1 | 2,1
  // 0,2 | 1,2 | 2,2
  def gameState : Status = {
    val (placements, movesLeft) = state.foldLeft(Map[String, Owner](), 0) { (result, tile) =>
      val (collection, movesLeft) = result

      def newVal(key: String) = {
        collection.get(key) match {
          case None => {
            tile.owner
          }
          case Some(owner) if owner == tile.owner => {
            owner
          }
          case _ => {
            None
          }
        }
      }

      val (x, y) = (tile.loc % 3, tile.loc / 3)

      val rowKey = s"row$y"
      val rowVal = newVal(rowKey)

      val colKey = s"col$x"
      val colVal = newVal(colKey)

      val diag1Key = "diag1"
      val diag1Val = newVal(diag1Key)

      val diag2Key = "diag2"
      val diag2Val = newVal(diag2Key)

      val newVals = if (x == y && x + y == 2) {
        Map(diag1Key -> diag1Val, diag2Key -> diag2Val, rowKey -> rowVal, colKey -> colVal)
      } else if (x == y) {
        Map(diag1Key -> diag1Val, rowKey -> rowVal, colKey -> colVal)
      } else if (x + y == 2) {
        Map(diag2Key -> diag2Val, rowKey -> rowVal, colKey -> colVal)
      } else {
        Map(rowKey -> rowVal, colKey -> colVal)
      }

      val move = if (tile.owner.isEmpty) 1 else 0
      (collection ++ newVals, movesLeft + move)
    }

    def findWinner(values: List[Owner]) : Status = values match {
      case List() => InProgress()
      case head :: tail => head match {
        case Some(x) => Winner(x)
        case None => findWinner(tail)
      }
    }

    if (movesLeft == 0) {
      Draw()
    } else {
      findWinner(placements.values.toList)
    }
  }

  override def toString(): String = {
    state.grouped(3).map(_.mkString("|")).mkString("\n")
  }
}

def playGame(game: Board = new Board()) : Unit = {
  game.gameState match {
    case Winner(player) => println(s"$player is victorious!\n$game")
    case Draw() => println(s"There are no moves left. It's a stupid tie.\n$game")
    case InProgress() => {
      try {
        game.nextPlayer match {
          case X() => println("Player 1, your move")
          case O() => println("Player 2, your move")
        }
        println(s"$game\n")

        val nextMove = scala.io.StdIn.readInt()

        playGame(game.move(nextMove))
      } catch {
        case e: Throwable => {
          println(s"Error making a move\n${e.getMessage}")
          playGame(game)
        }
      }
    }
  }
}

playGame()

The Highlights

I’m pretty proud of it. It highlights the best parts of Scala which are the case classes, match statements, and decomposing an object in a match statement.

Case Classes

Case classes are like fancy, extendable enums. They can inherit from other classes, override default methods like toString, and have new methods. We can create a fake-enum type by having our case classes inherit from a Player base class.

class Player()
case class X() extends Player
case class O() extends Player

val x1 = X()
val x2 = X()

// true
x1 == x2

Case classes can have constructor arguments. This lets us associate a value with the enum.

class Status()
case class Winner(player: Player) extends Status
case class InProgress() extends Status
case class Draw() extends Status

val w1 = Winner(X())
val w2 = Winner(O())

// false
w1 == w2

Match Statements

Match statements are like fancy switch statements. They evaluate in order and can include additional logic. In Scala the underscore is the we don’t care character. In a switch statement it will always evaluate. Because it’s always evaluated, it should always be your last case match.

collection.get(key) match {
  case None => {
    tile.owner
  }
  // Checks if collection.get(key) matches the type Some(x)
  // and that the value of
  case Some(owner) if owner == tile.owner => {
    owner
  }
  // The underscore will always evaluate.
  case _ => {
    None
  }
}

Decomposing an Object

Match statements can also decompose an object into variables for you if it matches a specific format.

def move(loc: Int) : Board = {
  val (front, back) = state.splitAt(loc)

  back match {
    case Tile(_, None) :: tail => {
      val newState = front ::: new Tile(loc, Some(nextPlayer)) :: tail
      new Board(newState, otherPlayer)
    }
    case _ => throw new Error(s"Invalid location $loc")
  }
}

In our move method, we use a match statement to split a List into its head and its tail. Outside of a match statement the line head :: tail is appending head to the front of the List tail using the :: operator. Match is checking if your variable could be deconstructed to match this pattern.

We check that the head, the Tile at the specified location, is empty. Then we insert a new tile. Any operator beginning with a colon is right associative. head :: tail will insert head at the front of the List tail.

val newState = front ::: new Tile(loc, Some(nextPlayer)) :: tail

Below is the same code refactored to show which variable is calling the method.

val newState = (tail.::(new Tile(loc, Some(nextPlayer))).:::(front)

The Problems

Maybe our board should’ve been a Vector of Tiles instead of a List. Lists in Scala are linked lists. Linked lists are great if we want to have immutable collections, but it’s kind of hard to think about when it’s new to you. Vectors would’ve let us access tiles by their index and simplify code.

For example, we need to iterate through the List to the tile’s location to check if a it is empty or not. This is only optimal if the user’s input is valid. If the input is invalid, we have to iterate through the list multiple times. If we used a Vector, we could create a separate method to check if a move is valid instead of trying to save operations by shoving the check into our move method.

Using a Vector to check if a move is valid would fix another problem. Our game loop is recursive. Because we catch invalid input for the moves in a try catch block, it’s not tail recursive. When we call playGame, the code in the catch block could still execute. Tic tac toe has at most nine moves. If our game was more complex, we could actually cause a stack overflow.

// This is badly designed
try {
  game.nextPlayer match {
    case X() => println("Player 1, your move")
    case O() => println("Player 2, your move")
  }
  println(s"$game\n")

  val nextMove = scala.io.StdIn.readInt()

  playGame(game.move(nextMove))
} catch {
  case e: Throwable => {
    println(s"Error making a move\n${e.getMessage}")
    playGame(game)
  }
}

Overall, I think using immutable values made things more complex. I spent most of my time writing the gameState method. I wanted to use Lists and foldLeft to build the results. If I allowed myself to use mutable values, I would have just used a for loop to check each of the rows, columns, and diagonals for a winner.

// Trying to force it
val (placements, movesLeft) = state.foldLeft(Map[String, Owner](), 0) { (result, tile) =>
  val (collection, movesLeft) = result

  def newVal(key: String) = {
    collection.get(key) match {
      case None => {
        tile.owner
      }
      case Some(owner) if owner == tile.owner => {
        owner
      }
      case _ => {
        None
      }
    }
  }

  val (x, y) = (tile.loc % 3, tile.loc / 3)

  val rowKey = s"row$y"
  val rowVal = newVal(rowKey)

  val colKey = s"col$x"
  val colVal = newVal(colKey)

  val diag1Key = "diag1"
  val diag1Val = newVal(diag1Key)

  val diag2Key = "diag2"
  val diag2Val = newVal(diag2Key)

  val newVals = if (x == y && x + y == 2) {
    Map(diag1Key -> diag1Val, diag2Key -> diag2Val, rowKey -> rowVal, colKey -> colVal)
  } else if (x == y) {
    Map(diag1Key -> diag1Val, rowKey -> rowVal, colKey -> colVal)
  } else if (x + y == 2) {
    Map(diag2Key -> diag2Val, rowKey -> rowVal, colKey -> colVal)
  } else {
    Map(rowKey -> rowVal, colKey -> colVal)
  }

  val move = if (tile.owner.isEmpty) 1 else 0
  (collection ++ newVals, movesLeft + move)
}

Asides

  1. Read Evaluate Print Loop. The best parts of Ruby and Scala are testing code snippets using their REPLs. Every programming language needs one. I don't know how you can code without it.
  2. Let's say you're decomposing a tuple. If you only care about one of the values, you can use the underscore to ignore the other
        // We only need one value out of the tuple
        val (x, _) = (500, "days")
        
  3. Outside of my tic tac toe code, which is entirely my bad code, Scala has issues. Its immutability and static typing is a huge pain when you're trying to parse JSON.