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.