简体   繁体   中英

Similar to for-loop in functional Scala

I want to create a simulation model to learn Scala as well as functional programming (FP). I already have all the logic: creation of a population of agents (it's just a List[Agent] , where Agent is a class defining an individual member, like a particle in a gas) and some functions (moving in space, for example) that "act" on the population.

My problem comes when I want to apply the same functions over the initial population several times because of immutability in FP. I want to apply those functions to the initial population for N rounds (where a round is defined when all the functions have been applied). I don't know how to deal with immutable values between rounds.

Usually, I would do a for-loop where a variable changes its value, but how do you deal with this when values are immutable?

My code now looks like this:

object Main extends App {
    val soc = Society(numAgents = 1000)        // Create a Society

    val agents = soc.initSociety()             // Init the society
    val movedAgents = soc.moveAgents(agents)   // Move the agents
}

The method are defined such that they return a List[Agent] , so the type is always the same.

I've seen some solutions using foldleft , but I need to apply the function moveAgents to what it returns.

You can get the return value of moveAgents with folds. If you simply want to call the moveAgents method n times, you can do this

val newAgents = (1 to n).foldLeft(soc.initSociety()) { (a, i) => soc.moveAgents(a) }

This is the equivalent of doing soc.moveAgents(soc.moveAgents(...(soc.initSociety()))) with n calls to moveAgents

If you have multiple functions that you want to apply (with a different one each round), you can do the same thing:

// n/3 because there are 3 functions
val newAgents = (1 to n/3).foldLeft(soc.initSociety()) { (a, i) => f3(f2(f1(a))) }

If you have, say, a List of functions, you can try this:

val fs = List(f1, f2, f3)
val newAgents = (1 to (n/fs.size)).foldLeft(soc.initSociety()){ (a, i) => fs.foldLeft(a){ (ag, f) => f(ag) } }

Well any simple for loop can be easily rewritten as a tal-recursion, and a (tail) recursion can usually be written as a foldLeft .

First approach, simple loop.

def stimulate(soc: Society, n: Int): List[Agent] = {
  var agents = soc.initSociety()
  for (i <- 0 to n) {
    agents = soc.moveAgents(agents)
  }
  agents
}

Second approach, recursion.
(let's remove that var)

def stimulate(soc: Society, n: Int): List[Agent] = {
  @annotation.tailrec
  def loop(i: Int, agents: List[Agent]): List[Agent] =
    if (i < n) loop(i + 1, agents = soc.moveAgents(agents))
    else agents

  loop(i = 0, agents = soc.initSociety())
}

Third approach, fold.
(let's remove the boilerplate from the recursion)

def stimulate(soc: Society, n: Int): List[Agent] =
  (0 to n).foldLeft(soc.initSociety()) { case (agents, _) =>
    soc.moveAgents(agents)
  }

If the intermediate values between each round is of any interest...

val rounds = List.iterate(agents, n)(f _ andThen g andThen h)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM