I've been cooking with gas since I got Daniel C Sobral's help on my last question. I am now re-reading Odersky's "Programming in Scala, 2nd Edition" (finished my first reading about this time last year).
I am eager to understand how to alter my mental modeling of problems to more fully embrace the functional programming style. However, I have spent hours looking at the code below attempting to figure out how to eliminate the var references. I am sure my imperative past is overshadowing and blinding me to functional possibilities.
I think I have retained overall "referential transparency" at each method; i.e. none of the var-ness escapes the local scope of the method (or function) within which it is defined. However, I would like to understand how I might achieve a higher level of functional programming purity, even if it is slightly unreasonable, within each method. I am more looking for ways I need to change my problem solving approaches to be more myopically functional in nature.
Specifically, how might I approach eliminate each instance of var.
Thank you for any guidance.
case class Bitmap2d(name: String, rowsByColumns: List[List[Boolean]], faceUp: Boolean) {
//require(rowsByColumns != null) //Assumed that if null was allowed as parameter, an Option would be used
require(validateRectangular, "all rows must have same length")
def validateRectangular: Boolean = {
rowsByColumns.forall(_.size == rowsByColumns.head.size)
}
}
class Piece(name: String, charRep: Char, rowsByColumnsAndUp: List[List[Boolean]]) {
val translations = createTranslations()
def printTranslations() = {
println("Name: " + name + " Char: " + charRep)
for (bitmap2d <- translations) {
println(" Orientation: " + bitmap2d.name)
for (row <- bitmap2d.rowsByColumns)
{
for (pixel <- row)
{
val value = if (pixel) "1" else "0"
print(value);
}
println()
}
}
}
private def createTranslations() = {
//generate all 7 translations
val bitmap2dRaws =
for (i <- 0 to 7)
yield translateBasedOnBitsForXYR(rowsByColumnsAndUp, i)
var bitmaps = Set[List[List[Boolean]]]()
var result = List[Bitmap2d]()
for (
bitmap2dRaw <- bitmap2dRaws
if (!bitmaps.contains(bitmap2dRaw._2))
)
{
bitmaps += bitmap2dRaw._2;
result = Bitmap2d(bitmap2dRaw._1, bitmap2dRaw._2, bitmap2dRaw._3) :: result
}
result.reverse
}
private def translationSideUp(bits: Int) = {
val flipX = ((bits & 1) == 1)
val flipY = ((bits & 2) == 2)
((flipX || flipY) && (!(flipX && flipY)))
}
private def translationDescription(bits: Int) = {
var result = List[String]()
if ((bits & 1) == 1) {
result = "FlipX" :: result
}
if ((bits & 2) == 2) {
result = "FlipY" :: result
}
if ((bits & 4) == 4) {
result = "Rotate" :: result
}
result.reverse
}
private def translateBasedOnBitsForXYR(rowsByColumns: List[List[Boolean]], bits: Int) = {
require (((bits >= 0) && (bits < 8)), "bits must contain a value between 0 (inclusive) and 8 (exclusive)")
var result = rowsByColumns;
if ((bits & 1) == 1) {
result = translateAroundXAxis(result)
}
if ((bits & 2) == 2) {
result = translateAroundYAxis(result)
}
if ((bits & 4) == 4) {
result = translateRotate90DegreesRight(result)
}
(translationDescription(bits).mkString("+") , result, translationSideUp(bits))
}
private def translateAroundXAxis(rowsByColumns: List[List[Boolean]]) = {
if (rowsByColumns.size > 1) {
rowsByColumns.reverse
}
else {
rowsByColumns
}
}
private def translateAroundYAxis(rowsByColumns: List[List[Boolean]]) = {
if (rowsByColumns.head.size > 1) {
for (row <- rowsByColumns)
yield row.reverse
}
else {
rowsByColumns
}
}
private def translateRotate90DegreesRight(rowsByColumns: List[List[Boolean]]) = {
val width = rowsByColumns.head.size
val height = rowsByColumns.size
val linearized = //need non-recursive random access
(
for {
row <- rowsByColumns
pixel <- row
} yield pixel
).toArray
var result = List[List[Boolean]]()
for (i <- 0 to (width - 1)) {
var tempRow = List[Boolean]()
for (j <- 0 to (height - 1)) {
tempRow = linearized((width * (j + 1)) - 1 - i) :: tempRow
}
result = tempRow :: result
}
result
}
}
UPDATE:
Per Daniel C Sobral's excellent guidance, I have working through his answer. And and now also Landei's. I've reworked the Piece class rather than just copying/pasting their answers. Below is the modified class. It's quite a bit smaller. And I've learned quite a bit about how to reframe my imperative loop based thinking to using recursion (and have only just begun to understand foldLeft).
Thank you again, Daniel and Landei for your feedback and guidance. If you see anything else that I can improve or about which I could think more functionally, please let me know and I will do the work to apply it.
import scala.annotation.tailrec
import scala.collection.immutable.TreeMap
object Piece {
val ROTATION_DESCRIPTIONS = TreeMap(1 -> "FlipX", 2 -> "FlipY", 4 -> "Rotate")
}
class Piece(name: String, charRep: Char, rowsByColumnsAndUp: List[List[Boolean]]) {
val translations = createTranslations()
def printTranslations() = {
println("Name: " + name + " Char: " + charRep)
for (bitmap2d <- translations) {
println(" Orientation: " + bitmap2d.name + "-" + (if (bitmap2d.faceUp) "Up" else "Down"))
for (row <- bitmap2d.rowsByColumns) {
for (pixel <- row) {
print(if (pixel) "1" else "0");
}
println()
}
}
}
private def createTranslations() = {
@tailrec def recursive(bits: Int,
bitmapOnlys: Set[List[List[Boolean]]],
bitmap2ds: List[Bitmap2d]): List[Bitmap2d] = {
if (bits > 7) {
bitmap2ds
}
else {
val translation = translateBasedOnBitsForXYR(bits);
val add = (!bitmapOnlys.contains(translation))
recursive(bits + 1,
if (add) bitmapOnlys + translation else bitmapOnlys,
if (add) Bitmap2d(translationDescription(bits).mkString("+"),
translation, translationSideUp(bits)) :: bitmap2ds else bitmap2ds
)
}
}
recursive(0, Set[List[List[Boolean]]](), List[Bitmap2d]()).reverse
}
private def translationSideUp(bits: Int) = (!((bits & 1) == 1) ^ ((bits & 2) == 2))
private def translationDescription(bits: Int) =
Piece.ROTATION_DESCRIPTIONS.filterKeys(x => (bits & x) == x).values.toList
private def translateBasedOnBitsForXYR(bits: Int) = {
require (((bits >= 0) && (bits < 8)),
"bits must contain a value between 0 (inclusive) and 8 (exclusive)")
val tx = if ((bits & 1) == 1) translateAroundXAxis(rowsByColumnsAndUp)
else rowsByColumnsAndUp
val ty = if ((bits & 2) == 2) translateAroundYAxis(tx) else tx
if ((bits & 4) == 4) translateRotate90DegreesRight(ty) else ty
}
private def translateAroundXAxis(rowsByColumns: List[List[Boolean]]) = {
if (rowsByColumns.size > 1) {
rowsByColumns.reverse
}
else {
rowsByColumns
}
}
private def translateAroundYAxis(rowsByColumns: List[List[Boolean]]) = {
if (rowsByColumns.head.size > 1) {
for (row <- rowsByColumns)
yield row.reverse
}
else {
rowsByColumns
}
}
private def translateRotate90DegreesRight(rowsByColumns: List[List[Boolean]]) = {
translateAroundYAxis(rowsByColumns.transpose)
}
}
value, only used once? Wouldn'tprint(if (pixel) "1" else "0")be at least just as readable? (Also, I'd suggest adding some line breaks for the extra-wide lines.) – Christopher Creutzig Apr 10 '12 at 17:44