Seven Languages In Seven Weeks: Scala - Day 1
After just barely surviving Prolog, it's time for the Scala chapter in my Seven Langauges in Seven Weeks book by Bruce Tate. Coming from a ColdFusion background, it's comforting to know that Scala runs on the JVM (Java Virtual Machine) in the same way that ColdFusion does; and, just as with ColdFusion, Scala has access to all of the core Java libraries that I typically use for fun and profit.
Unlike Java, however, Scala is not so ceremonious. You don't need semi-colons (the horror!). You also don't need to define data types; as long as you initialize your variables at the time of declaration, the compiler can generally infer the data type from the initial value. The use of parenthesis is also somewhat optional; although, they seem to follow a ruleset that I don't quite grasp yet. There's also what looks like Ruby and Io style code blocks that can act as inline functions. Oh, and return statements are optional - the last executed statement of any function is the implied return value. Of course, you have to be careful because it seems that all operations in Scala return a value, even if that value is "Unit" (which can generally be thought of as "void").
Because so much of the language is optional, there was a lot of compiling the code and letting the the compiler tell me where I need to make things more explicit. For example, you don't need to define return types for functions; but, if you use a return() statement, you do need to define a return type. This homework took me about 4 hours and involved a lot trial and error.
Also, special thanks to Sean Corfield who helped me figure out why the heck I was getting "Unit" and "Boolean" mismatched types. The default return type for a function is "Unit" (think "void"). However, if you don't use a return statement, the compiler will infer the appropriate return type based on the code within the given function. Seeing as I am in love with the return statement, however, the Scala compiler was not able to determine my return type; as such, my functions that returned Booleans were being seen as returning Unit. As Sean pointed out, either adding the Boolean return type OR removing the return statement would fix this error (although Sean also pointed out that removing the the return statements was more "proper" Scala).
HW1: Write a game that will take a tic-tac-toe board with X, O, and blank characters and detect the winner or whether there is a tie or no winner yet. Use classes where appropriate. BONUS: Let two players play tic-tac-toe.
I'm sure I went a little overboard on this one; but, the more complex I made the solution, the more opportunity I'd figure I'd have to get comfortable with the constructs of the Scala programming language. The way I saw it, a Tic Tac Toe game needed a few different classes:
TicTacToeCell - This represented a single cell on a Tic Tac Toe board. It managed the state of the given cell and acted as a gatekeeper to setting the occupant value.
TicTacToeBoard - This represented a Tic Tac Toe board which composed a list of 9 TicTacToeCell instances. The board was capable of determining if there were any possible moves left to be made, if there was a tie or a winner, and if there was a winner, who that winner was. The board itself makes no assumptions about game rules or who is playing.
TicTacToeGame - This was the class that was actually ran the Tic Tac Toe game on a given board. This was the class that worried about alternating players, populating the board, and stopping once a winner emerged or a board became exhausted.
// Write a game that will take a tic-tac-toe board with X, O, and
// blank characters and detect the winner or whether there is a tie
// or on winner yet. Use classes where appropriate.
//
// BONUS: Let two players play tic-tac-toe.
// I represent a single cell within a TicTacToe board.
class TicTacToeCell {
// I am the occupant of the cell. By default, there is no
// occupant of the cell, which is indicated by a blank
// character.
//
// NOTE: I tried to start out with Nil, but kept getting type
// conversion errors when setting it later on.
var occupant = " ";
// I am the list of valid occupants.
val validOccupants = List( "X", "O" );
// I compare the current cell to an incoming cell. Two cells
// are said to be equal if their occupants are the same.
def ==( target: TicTacToeCell ):Boolean = {
return( this.getOccupant() == target.getOccupant() );
}
// I return the current cell occupant.
def getOccupant():String = this.occupant;
// I determine if the current cell is occupied.
def isOccupied() = {
(this.getOccupant() != " ");
}
// I determine if the given occupant value would be valid. within
// this cell.
def isValidOccupant( occupant: String ) = {
this.validOccupants.contains( occupant );
}
// I set the given occupant.
def setOccupant( newOccupant: String ):Unit = {
// Check to make sure the current cell is not occupied.
if (this.isOccupied()){
// This cell can only be occupied once.
throw new IllegalStateException( "This cell is currently occupied by " + this.occupant + ". A cell can only be occupied once." );
}
// Check to make sure that the incoming occupant is valud.
if (!this.isValidOccupant( newOccupant )){
// The incoming occupant needs to be valid.
throw new IllegalArgumentException( "The incoming occupant is not valid. Valid types include: " + this.validOccupants );
}
// If we made it this far, then the cell can accept the
// incoming occupant. Store it.
this.occupant = newOccupant;
}
// Override the toString() method to allow the cell to be
// converted to a string reprentation as a function of it
// current occupant.
override def toString():String = {
// Check to see if the cell is occupied - this will determine
// how we want to represent the cell.
if (this.isOccupied()){
// Returnt the occupant.
return( this.getOccupant() );
} else {
// Return a dot. This is simply so that I can copy/paste
// this on my b.blog without losing multiple empty spaces
// in a row.
return( "." );
}
}
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// I represent a TicTacToe board.
class TicTacToeBoard {
// Our board is just going to be a list. The indices of the
// board will go from 0 to 8; 0 being the top-left and 8
// being the bottom-right.
val board = List(
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell(),
new TicTacToeCell()
);
// Define all the paths that can contain a winner. A winner
// is three in a row in any of the horizontal, vertical, or
// diagonal paths.
val winningPaths = List(
List( 0, 1, 2 ),
List( 3, 4, 5 ),
List( 6, 7, 8 ),
List( 0, 3, 6 ),
List( 1, 4, 7 ),
List( 2, 5, 8 ),
List( 0, 4, 8 ),
List( 2, 4, 6 )
);
// I get the winner of the board.
def getWinner():String = {
// Check to make sure there is a winner before we try and
// find it on the board.
if (!this.hasWinner){
// There has to be a winner before one can be found.
throw new IllegalStateException( "The board does not have a winner." );
}
// Loop over the paths to check each one against the board.
// Once we find the winning path, we'll know which occupant
// was the winner.
this.winningPaths.foreach( path =>
// Check to see if this path contains a winner.
if (this.pathContainsWinner( path )){
// Return the first occupant of this winning path.
return(
this.board( path( 0 ) ).getOccupant()
);
}
);
// We'll never make it this far, but the compiler doesn't
// know this. As such, it appears that I need a return
// statement here to not get a type mismatch on return type.
return( "SHOULD NEVER MAKE IT THIS FAR" );
}
// I determine if there are any empty cells on the baord.
def hasFreeCells():Boolean = {
// Iterate over the cells to determine if any of them have
// yet to be occupied.
this.board.foreach( cell =>
// Check to see if the current cell is occupied.
if (!cell.isOccupied()){
// Since the cell is NOT occupied, then we can
// short-circuit our testing and return - no need
// to check any more cells.
return( true );
}
);
// If we made it this far then we could not find any empty
// cells.
return( false );
}
// I determine if the board has more moves. This will true if
// there are empty cells AND there is no winner yet.
def hasNextMove() = {
(this.hasFreeCells() && !this.hasWinner);
};
// I determine if the board has a winner.
def hasWinner:Boolean = {
// Loop over the paths to check each one against the board.
this.winningPaths.foreach( path =>
// Check to see if this path contains a winner.
if (this.pathContainsWinner( path )){
// The board has a winner.
return( true );
}
);
// If we made it this far then none of the possible WIN paths
// contain winners.
return( false );
}
// I determine if board is a tie - this is basically the case if
// there are no more free cells AND there is NO winner.
def isTieBoard() = {
(!this.hasFreeCells() && !this.hasWinner);
}
// I determine if the given path contains a winner.
def pathContainsWinner( path: List[Int] ) = {
// Get the cells at the given path indices.
val cell1 = this.board( path( 0 ) );
val cell2 = this.board( path( 1 ) );
val cell3 = this.board( path( 2 ) );
// Determine if the given cells constitue a winner.
cell1.isOccupied() &&
(cell1 == cell2) &&
(cell1 == cell3);
}
// I place the given occupant in the given cell. This method
// with throw an error if the index is either out of bounds or
// the given cell is already occupied.
def setOccupant( cellIndex: Int, occupant: String ) = {
this.board( cellIndex ).setOccupant( occupant );
};
// I override the toString() method to allow for pretty printing
// of the board. This assumes that each Cell can be implicitly
// turned into a string using the implied toString() method.
override def toString():String = {
return(
this.board( 0 ) + " | " +
this.board( 1 ) + " | " +
this.board( 2 ) + "\n" +
"---------\n" +
this.board( 3 ) + " | " +
this.board( 4 ) + " | " +
this.board( 5 ) + "\n" +
"---------\n" +
this.board( 6 ) + " | " +
this.board( 7 ) + " | " +
this.board( 8 ) + "\n"
);
};
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// I play a tic tac toe game using the tic-tac-toe board.
class TicTacToeGame {
// Start the game play!! It's GO TIME!
// --------------------------- //
this.play();
// --------------------------- //
// I return the next player for the given player. Basically,
// I know how to toggle between X and O.
def getNextPlayer( player: String ):String = {
// Check to see what the current player is.
if (player == "X"){
// Toggle to O.
return( "O" );
} else {
// Toggle to X.
return( "X" );
}
}
// I play the game.
def play():Unit = {
// Create a new board to play.
var board = new TicTacToeBoard();
// Keep track of which player is current.
var currentPlayer = "X";
// Print a new line for readability.
println( "" );
// Start playing and keep playing while the board has a next
// available move on it. Once we are out of movies, we can
// check to see if there is a tie or if there is a winner.
while( board.hasNextMove() ){
// Print the current board.
println( board );
// Print the current player's move.
print( "\n" + currentPlayer + "'s Move: " );
// Get the index value and set the occupant. This will
// raise an exception if the index is out of bounds OR
// if the given cell is already occupied.
try {
// Get the current player's chosen index from the
// console prompt.
var cellIndex = Console.readInt();
// Populate the cell with the given player.
board.setOccupant( cellIndex, currentPlayer );
// Switch the player so the next person can take
// their turn.
currentPlayer = this.getNextPlayer( currentPlayer );
} catch {
// The selected index is not valid.
case e:Exception =>
println( "That index is not valid!" );
}
// Print a new line before the board is output.
println( "" );
}
// If we've made it this far then there are not more moves
// on the given board. Now, we need to check to see if this
// is because it was a tie or because someone actually won.
// Print out the final state of the board.
println( board );
// If we have gotten this far then there are no more moves.
// Either someone has won or there is tie.
if (board.isTieBoard()){
// Output the tie game message.
println( "Tie Game!" );
} else {
// Get the winner.
var winner = board.getWinner();
// Output the winner message.
println( "And the winner is: " + winner );
println( "Hate the game, not the player!" );
}
// Now, check to see if the players want to go at it again.
print( "\nWant to play again? (Y/N) " );
// Get the user's response.
var playAgain = Console.readLine().toLowerCase();
// Anything but an "n" or empty space will play again.
if ((playAgain != "") && (playAgain != "n")){
// Play again!
this.play();
}
}
}
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// Create a new game (this will start playing automatically).
var game = new TicTacToeGame();
As you can see, a Scala code file can contain both class definitions and procedural code. In the above file, I am defining my three classes before I instantiate a TicTacToeGame class and kick off the game play. Doing so resulted in the following console output:
. | . | .
---------
. | . | .
---------
. | . | .X's Move: 0
X | . | .
---------
. | . | .
---------
. | . | .O's Move: 1
X | O | .
---------
. | . | .
---------
. | . | .X's Move: 2
X | O | X
---------
. | . | .
---------
. | . | .O's Move: 3
X | O | X
---------
O | . | .
---------
. | . | .X's Move: 4
X | O | X
---------
O | X | .
---------
. | . | .O's Move: 5
X | O | X
---------
O | X | O
---------
. | . | .X's Move: 6
X | O | X
---------
O | X | O
---------
X | . | .And the winner is: X
Hate the game, not the player!Want to play again? (Y/N) N
Choosing the moves in the game is kind of a pain in the butt - the user has to pick the index of the cell within the list of composed cells. That means the user has to not only understand the data structure, she has to be able to mentally map the 3x3 grid onto a 0-8 list. Yes, very lame.
After Prolog, being back in a "traditional" programming language feels like sweet relief. Scala definitely has its own set of quirks (ex. a recursive function needs to have a declared return type), but once you get past some of those peculiarities, the language is fun to work with. So far, I haven't demonstrated anything in Scala that makes the language all that special; according to Tate, though, the language will really start to shine when we see how well it integrates Object-Oriented Programming with Functional Programming.
Want to use code from this post? Check out the license.
Reader Comments
Extra credit: Create an artificial intelligence to play either Chess or Global Thermonuclear War.
@Justin,
Ha ha ha, that might have to wait till day 2 :D
All those semicolons burn my eyes! :)
It's interesting to see other people's Scala code because, like C++, Scala is very much a multi-paradigm language and it allows procedural code, OO code and functional code... together or in isolation.
Scala's optional punctuation is intended to allow more English-like code to be written, without syntactic noise, and it becomes more so when you write in a more functional style since the code becomes more of a single flow than a series of instructions.
When I get to the Scala chapter, I'll try to remember to do this homework and publish it for a compare and contrast exercise.
Great series, Ben! Keep it up!
@Sean,
I am really looking forward to the "functional" programming stuff. I really have no idea what functional programming is, and all this talk of mutable and immutable state and parallel processing tasks seems very exciting.
The strangest thing that I ran into yesterday was that I had to give a function a return() statement that would never be reached - the actual function will return out from a .foreach() block; however, the compiler wouldn't let the code compile until I had an extraneous return() value after the foreach() statement. Very odd.
@Ben,
A foreach doesn't have a value (it yields Unit) so if there is a theoretical path past that statement, Scala will need a return value (and because you had an explicit return inside the loop, you need an explicit return elsewhere - a single exit point can be a value, multiple exit points must be return statements).
A more idiomatic solution would be to 'find' using a predicate block and that would return an Option[T] type and then you could 'match' on the result (and have an empty _ case return "") or use 'getOrElse'.
Everything being an expression that returns a value can take a bit of getting used to, as can the functional tenet of immutable state - which in turn means no assignments and no 'loops' in the traditional sense of iterating over something and performing a (procedural) operation.
You'll get more of a sense of this from Clojure and Haskell as you get to those chapters :)
@Sean,
I'm finding one hurdle to be the documentation. The API seems to be extremely robust, but not very explanatory. For example, I looked up List::find that you suggested as a better alternative and here is how the API defines it:
def find (p: (A) ? Boolean) : Option[A]
Finds the first element of the list satisfying a predicate, if any.
I am sure this kind of notation starts to make sense after a while; but, as a newcomer to the language, I am finding this to be unreadable.
Anyway, we'll see what day #2 holds in tonight's homework!! Very exciting stuff!
@Ben,
p: (A) => Boolean
A parameter p of type function (=>) that takes a single argument of type A and returns a Boolean.
find() itself returns a value of type Option[A] which is either Some(a) or None, where a is of type A.
Option[T] is Scala's way of safely avoiding null pointers. You can call .getOrElse(defaultValue) on it and either get the value (if present - Some) or the supplied default value (if not present - None).
@Sean,
I ran into the Some/None thing last night. Took me a few minutes to figure out what was going on. I was searching for a regular expression match in a given string which apparently returns Some/None. I ended up having to use pattern matching (not the regular expression kind) to deal with the result:
The whole concept of pattern matching and partial functions is confusing to say the least. It's such a change from anything I've ever known that learning about it is quite challenging.
@Ben,
match is pretty powerful and goes far beyond regular expressions. This is structural type matching. findFirstIn() returns Option[String] so the values are either Some[String] or None, both of which extend Option[String]. A value of type Some[String] holds a string value. A value of type None holds nothing - it's a type-safe "null", if you like. By using types like this, the _compiler_ can ensure that your code is doing the right thing and won't blow up with a null pointer exception at runtime (unless you explicitly call an unsafe method, such as get() on the result of findFirstIn() - you can safely call getOrElse(defaultValue) tho').
HTH?
Sean
@Ben,
Oh, and partial functions are constructs that are defined for only part of their input range - but more importantly, you can ask them whether they are defined for a specific value, via isDefinedAt(value).
@Sean,
It's funny, I'm so used to thinking about "pattern matching" in terms of regular expressions that every time I saw "Scala Pattern Matching" in my Google search results, I was thoroughly confused by the resultant page :)
I get that they are different, but the terminology is just so deeply embedded in my mind that it confuses me at first.
The partial function stuff is just something that I can't relate to in ColdFusion at all. Like, take the given match{} call above, I assume that it is passing a partial function that is defined for both Some and None, so if I had a reference to it, I could call something like:
partialFunc.isDefinedAt( Some )
... or something like that. This concept totally blows my mind. My brain simply can't compute how a function's execution can be determined in such a way. I have nothing to relate it to.
@Ben,
Back in the 80's and early 90's I worked with a few languages that did structural matching (including Prolog). My introduction to the power of regex was actually quite late... :)
Partial function is more of a math term, I believe, where you have a domain (of values) and a mapping to another domain. For example, reciprocal (1/x) is a partial function - it is not defined for x = 0. squareRoot is a partial function - it is not defined for x < 0. With both of those, you can construct a PartialFunction in Scala that is able to answer .isDefinedAt(x) because the domain mapping is known.
With Some[T] and None, those are both extensions of Option[T]. Some(42) has type Some[Int] or by extension Option[Int]. So if a function f() returns Option[String] (for example), you can do:
val a = f().getOrElse("default")
If f() returns Some("str"), a will be "str". If f() returns None, a will be "default". However:
val a = f().get()
If f() returns None, this will blow up, just like a NullPointerException.
You can also match the result:
f() match {
case Some(a) => // do stuff with value a
case None => // handle no result
}
@Sean,
The .getOrElse() thing is a cool idea. Before I figured out how to use match{}, I was gonna use the getOrElse() to deal with the possible None return value.
I am sure this kind of stuff becomes second nature once you use it after a while. Perhaps it only works with Case statements - not sure.