This repository contains a text-based tic-tac-toe game written as a way to experiment with functional programming and persistent data structures in Java 8.
0 1 2
3 4 5
6 7 8
Player X, it's your turn, enter a number: 4
0 1 2
3 X 5
6 7 8
Player O, it's your turn, enter a number:
Make sure you have sbt and Java 8 installed on your system.
- To run the tic-tac-toe game:
sbt run
- To run the tests:
sbt test
See the [http://www.scala-sbt.org/0.13/tutorial/Running.html](SBT documentation) for other commands.
Java 8 introduced a number of new features to Java, including lambda expressions and streams that open the door to a more functional style of programming. I wrote this app to see what it was like to write a nearly pure functional app, subject to the following constraints:
- Immutable data: just about every variable and field is marked as final. All data structures are immutable.
- Persistent data structures: there is no need to worry about performance with a tic-tac-toe game, but for fun, I experimented with the persistent data structures from the pcollections library.
- Pure functions: the functions in the
TicTacToe
class are the only ones that performs I/O. All other functions are pure.
Here are the main highlights of the code:
Board.java
: an immutable representation of an n x n tic-tac-toe board that can be filled with X's and O's.GameState.java
: an immutable representation of a game, including theBoard
and the current player's turn. ThemakeMove
method places a new X or O on the board and returns a newGameState
. TheisGameOver
returns true if there is a winner or a tie. ThefindWinner
method returns the winner of the game orOptional.empty
if there is no winner.TicTacToe.java
: this class contains themain
method and is the only one that performs I/O. It starts with an emptyGameState
and usesStream.iterate
andGameState.makeMove
to generate newGameStates
based on user input untilGametate.isGameOver
is true.
Overall, Java 8 is a massive step forward for the language, but I'm not sure it's enough. It took much longer to write this app in a functional style than I expected due to lots of annoyances:
- The higher order functions in the
Stream API
, such asmap
,reduce
, andfilter
, are all designed to work in parallel. This may be useful for performance reasons, but to support parallel operation, the signatures of some of these methods are sometimes a little different than the equivalent methods in all other programming languages (e.g. there is nofoldLeft
orfoldRight
because a parallel operation doesn't go linearly from one side to the other). - The need to convert Java collections to a
Stream
(e.g. by calling.stream()
) and back (e.g. by calling.collect(Collectors.toList())
) in order to use higher order functions is verbose. I wish I could just callmap
directly on aList
orSet
. - Some code, such as determining if a
Board
had a winner, was much harder to do in an immutable fashion using theStream
API than with good old fashionedfor
,break
, andreturn
. If anyone knows a cleaner pattern for implementingBoard.findWinner()
, let me know. - It is tedious and verbose to mark every variable as
final
. I wish immutable was the default or there was a more concise construct, such as usingval
vsvar
in Scala. - It's not clear why numeric streams need special treatment, such as the
IntStream
andDoubleStream
classes and themapToInt
andmapToDouble
higher order functions. - I sorely missed Scala's sequence comprehensions
when I had to chain many
map
andflatMap
calls together. - Most collection APIs in Java do not provide immutability guarantees, not even the Java 8
Stream
APIs, so you have to be extra careful to use persistent collections or Guava's immutable collections. - While some Java code uses
Optional
, most still usesnull
. And, of course, you could even set anOptional
variable to null, so it's still very easy to hit aNullPointerException
. - The compile errors, at least in IntelliJ, were less helpful than usual. Many of them seemed to be caused by a minor error in a type signature causing type inference to fail and return a less-than-helpful "cyclic reference" error.
This code is available under the MIT license. See the LICENSE file for more info.