2
\$\begingroup\$

I am learning Scala, coming from Java background. Here I have written a small prototype proof of concept program that is a part of concept base for a simple upcoming tutorial game. There are two game objects: a Cell and a Breed of cells (a 1d row). The operations are simple. One can create a breed with a number of cells. Each cell can mutate with a custom function chosen randomly from the available set. If the breed mutates, all of it's cells mutate. My question is the one common today - how Scala-ish is my code? How to improve? Coming from OOP, I have witnessed a very strange feeling while writing this code: it feels just like the internals of objects leak out from their ripped bodies just like in a real OOP thriller movie. But everyone suggests breaking the code into functions... so where is the balance?

package main
import scala.util.Random

object Breed extends App{

val rand:Random = new Random

//mutations
def mutF(f:Int => Int)(v:Int) : Int = {f(v)}

def mutFA(v:Int) : Int = mutF(x => x)(v)
def mutFB(v:Int) : Int = mutF(x => x + x)(v)
def mutFC(v:Int) : Int = mutF(x => x * x)(v)

val mutations : List[Int=>Int] = List(mutFA, mutFB, mutFC)

//constants (sources)
val emptyCellList : List[Cell] = List[Cell]()

//mutations manipulations
def nextMutation(f:Int=>Int) : Int=>Int = {
    val i = mutations.indexOf(f) 
    if(i == mutations.length - 1) mutations(0)
    else mutations(i + 1)
}

def randomMutation : Int=>Int = {
    mutations(rand.nextInt(mutations.length))
}

//---CELL---
class Cell(f:Int=>Int, value:Int) {
    def mutate() = new Cell(nextMutation(f), f(value))
    override def toString = "[" + value + "]"
}

def cellsToString(l:List[Cell]) : String = {
    l match {
        case Nil => ""
        case y :: ys => y.toString + cellsToString(ys)
    }

}

//breeder
def createBreed(num:Int) : Breed = {

    def makeCell() : Cell = new Cell(randomMutation, rand.nextInt(10))

    def makeCells(acc:Int, cells:List[Cell]) : List[Cell] = {
        if(acc == 0) cells
        else makeCells(acc - 1, makeCell :: cells)
    }

    new Breed(0, makeCells(num, List[Cell]()))
}

def mutateBreed(gens:Int, breed:Breed) : Breed = {
    def mutate(acc:Int, b:Breed) : Breed = {
        if(acc == 0) b
        else {
            val mutated : Breed = b.mutate();
            println(b.output() + " ---> " + mutated.output())
            mutate(acc - 1, mutated)
        }
    }
    mutate(gens, breed)
}

//---BREED---
class Breed(generation:Int, cells:List[Cell]) {

    def mutateCells() : List[Cell] = {
        def f(l:List[Cell]) : List[Cell] = {
            l match {
                case Nil => Nil
                case x :: xs => x.mutate() :: f(xs)
            }
        }
        println("BREED: size=" + cells.length)
        f(cells)
    }

    def mutate() : Breed = new Breed(generation + 1, mutateCells)

    def output() : String = {
        "BREED: [" + generation + "] = " + cellsToString(cells)
    }
}


// /\/\/\/\/test/\/\/\/\
def test() {
    val breed = createBreed(10)
    mutateBreed(10, breed)
}

test
}
\$\endgroup\$
2
\$\begingroup\$

Some improvements:

  • Try to minimize all mutable state and side effecting methods to single places. Don't divide them through the whole program.
  • Scala is object oriented. Therefore use methods of the objects you want to work with instead of global functions.
  • use methods with an empty parameter list only if these methods has side effects.
  • search the API for methods you can work with.

That's what I came up with:

object Game {

  import util.Random

  val rand: Random = new Random
  val mutations: Seq[Int => Int] = Seq(x => x, x => x + x, x => x * x)

  case class Cell(f: Int => Int, value: Int) {
    def mutate = {
      def nextMutation: Int => Int =
        mutations(((mutations indexOf f) + 1) % mutations.size)
      Cell(nextMutation, f(value))
    }

    override def toString = "["+value+"]"
  }

  case class Breed(generation: Int, cells: Seq[Cell]) {
    def mutate: Breed =
      Breed(generation + 1, cells map { _.mutate })

    override def toString: String =
      "BREED: ["+generation+"] = "+cells.mkString
  }

  def makeCells(num: Int) = {
    def randomMutation =
      mutations(rand nextInt mutations.size)

    def makeCell =
      Cell(randomMutation, (rand nextInt 10))

    (Seq fill num) { makeCell }
  }

  def test() {
    val firstBreed = Breed(0, makeCells(10))
    val breeds = ((1 to 10).iterator scanLeft firstBreed) {
      (b, _) => b.mutate
    }
    printBreeds(breeds)
  }

  def printBreeds(breeds: Iterator[Breed]) {
    (breeds sliding 2) foreach {
      case Seq(b, mutated) =>
        println("BREED: size="+b.cells.size)
        println(b+" ---> "+mutated)
    }
  }

}
\$\endgroup\$
  • \$\begingroup\$ Wow! Thanks, Antoras, your code clearly gave me a whole bunch of insights! There are numerous places that I see now, can be improved. I see you are making a heavy use of Seq that results in numerous cool usecases like pattern matching, mapping, iterating, currying and so on. That is what it is for functional programming. I feel like printing both source codes and posting them on the wall so I could meditate on comparing them from time to time) I also remember that I was reading JavaDocs just for fun when beginning with Java. So I guess the same would be true for Scala too. \$\endgroup\$ – noncom Dec 24 '11 at 10:47
  • \$\begingroup\$ I have noticed that you are using case classes here. That allows the corresponding functions to return Cell and Breed objects without the 'new' keyword. Why is this a preference here? Is it something aiming the future where I could use the both classes as case classes for say pattern matching, or is there some other benefit? \$\endgroup\$ – noncom Dec 24 '11 at 11:18
  • \$\begingroup\$ btw, sorry, not enough reputation to rate you up yet \$\endgroup\$ – noncom Dec 24 '11 at 11:37
  • \$\begingroup\$ I use case classes whenever there are a lot of object instantiations. I think it is easier to read and it is more obvious that the object stands for an immutable state. case classes have some advantages compared to normal classes. You mentioned already pattern matching, but there also some useful methods like copy or productIterator. \$\endgroup\$ – kiritsuku Dec 28 '11 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.