Authored by Win Myo Htet
In part 2, we get to the method whoWon and I am stuck there. I couldn't understand the code here on ward. I am thinking that I want to see the moving part of the code literally. I have been just reading the code visualizing the flow in my own mind. Now that I cannot understand the code, I can't visualize the code flow. I want to see them. Then, I notice that what I have been missing is time-tested-log-print-out from these methods. At first, I don't even know where and how to add the logging print statement in this tightly compacted code. I look and look, and start to see those one liner code. I made them two liner code.
One liner code
private def horizMoves(board: Board, x: Int, y: Int) = for (xx <- x until board.size) yield board(xx)(y)
Two liner code
private def horizMoves(board: Board, x: Int, y: Int) ={println("horizMoves:"+x+","+y); for (xx <- x until board.size) yield board(xx)(y)}
From my very limited exposure of the functional programming code (well, only this Tic Tac Toe code ;) ), I start to make assumption. No wonder it is said that functional programming is hard. FP is touted as a very powerful language and thus, it will only need a few lines of code where else OO and other imperative languages are very verbose. So FP dev would make an effort to show off their prowess by writing as few line as they can. There is nothing wrong with that. Of course, I would like to show off too. Well, the motive of writing a very compacted code has side effects. It requires more efforts on the next person to understand the code and to modify the code since the codes are tightly/compactly integrated. The developers might overlook the benefit of a few more lines of code because they want to write this very cool compacted code. I might be wrong, please enlighten me. For me, method won_? is a testament to that. It is a one liner code with foldLeft (I will come back for this foldLeft later). After I added the print log statement and watching the printed logs, I kinda notice that it can have less execution of code by one condition statement.
private def won_?(moves: Seq[Option[Move]], m: Move): Boolean
= {println(m+":"+moves)
if (moves.size<WinCount) return false
moves.foldLeft(List(0)) {
case (count :: rest, Some(`m`)) =>println(count+"::"+rest); count + 1 :: rest
case (counts, _) =>println("counts : "+counts); 0 :: counts
}.max >= WinCount}
In method won_? , from the printed log, I notice that it is unnecessary to process on collection of size less than 3. Why not do the check and return if the collection size is less than 3 instead of processing everything.
To be honest, I am awed by Oleg, the original developer of this code. I wonder how he can manage to think up these code without needing a print statement to see the flow. I also wonder if I will get his mental prowess of thinking up all these code in my mind when I am proficient enough in FP. Well, printing a log statement still pays tho. In method whoWon, where I am stuck at, I am able to reduce more execution because of the printed log. In the printed log, I come to see that this method is processing both X and O while the current mover can only win or draw, thus the other mover does not matter, so we only need to process current mover. Finally, it is also the printed statement, which I have inserted there after dismantling some code, and which lead me to the understanding of this code block and I am able to reduce more code execution.
def whoWon(game: Finished[Board],turn:Move): Option[Move] ={println("\nwhoWon game turn : Function called \n==========================") val forYield=(for { x <- 0 until game.board.size y <- 0 until game.board(0).size curr <- game.board(x)(y) if curr==turn if won_?(game.board, curr, x, y) } yield Some(curr)) println("forYield : "+forYield) forYield.find (_.isDefined) getOrElse None }
Here, I have modified whoWon and added another paramter Move object turn and if condition statement to make sure it is only executed for current mover. I would like to think that's 75% performance improvement. I have also introduced variable forYield because I am having a hard time understanding what the yield line does. So instead of returning directly from one liner, I have introduced print log at the top, a variable and print log for that variable. Then, I notice that the developer has written the code with no (programming)break mindset. Thus, the loop is allowed to finish and only after the loop, the validity of the result is checked before returning. Below is the revised code which will not need to do complete loop. If the result is valid, it will just return, thus not needing the break as intended in Scala design. When there is no valid result, the loop will finish and None is return. It's just that we developers are so used to a break from other languages.
def whoWon(game: Finished[Board],turn:Move): Option[Move] ={println("\nwhoWon game turn : Function called \n==========================") (for { x <- 0 until game.board.size y <- 0 until game.board(0).size curr <- game.board(x)(y) if curr==turn if won_?(game.board, curr, x, y) } return Some(curr)); return None }
I have been introducing new lines or replacing the code to increase the performance. Well, I also reduce the code to increase the performance.
Shall we say, 1.5 liner code ?
private def finished_?(board: Board) =board.flatMap(_.flatten).size == board.size * board(0).size || whoWon(Finished(board)).isDefinedmy truly 1 liner code.
private def finished_?(board: Board) =whoWon(Finished(board)).isDefined
In my 1 liner case, we don't even need finished_? and put its one line code where it is being used.
Here is the fix for the exception indexOutofBound bug mentioned in blog part 2
Here is the bug
case List(Int(x), Int(y)) if x < 3 && y < 3 => Some(Position(x, y))
Here is the fix
case List(Int(x), Int(y)) if (0 until 3 contains x) && (0 until 3 contains y) => Some(Position(x, y))
So I have put all the code that I have modified here and stated my assumption and reasoning behind these modification. I have been tinkering with this modification as I am learning and studying Scala and the code itself. I might be making the wrong assumption or making an unnecessary modification which can result in unaccounted cases of exceptions or they can be of not FP pattern and usage. Please do enlighten me.
Well, I still have left some of the codes I am stuck at. If you still have the stamina, please read on in the next part.
http://blog.aunndroid.com/2011/11/chewy-code-scala-tictactoe-part-4.html
Authored by Win Myo Htet
UPDATE
Oleg has written a blog on a very neat debug print method.
http://hacking-scala.posterous.com/side-effecting-without-braces
Authored by Win Myo Htet
No comments:
Post a Comment