How to return only the latest result of a function application on an iterator object with monad reader

156 views Asked by At

I translate my problem in this little "oversimplified" example. Objective is to return only the latest computation based on a simple function execution on each element of an iterator object.

val l = List(1,2,3,4).toIterator
def myPersonnalMultiply(number:Int) = number * 2

For this list, i want my iterator evaluation return '8'

I started to try with a yield , but it seems i cannot compute two things like that :

val result = for (
  e <- l
  computation = myPersonnalMultiply(e)
if ((l.hasNext) == false) yield computation

So, there is the solution to map then slice to conserve only the latest element evaluated, but after discussion with a collegue, it seems this is not really beautiful functionnal code compliant, and there is a better approach to do that (really ? ).

Based on this, i want to iterate and compute my function on all element of the list, but only the latest computation is returned.

Edit : my exemple is "too oversimplified", sorry for this mistake.

In reality, equivalent to myPersonallMultiply function equal an execution of a simulation which can need current state, or the previous state to be computed.

The states function of my simulator return an iterator of state :

 def states(implicit aprng: Random = new Random): Iterator[State] = {

    // Initial State loaded from file
    val loadedState = initState(this.getClass.getClassLoader.getResourceAsStream("init-situation.txt"))
    val territory = computeTerritory(loadedState)

    def simulationStep(state: State): State = {
        // 3b - Apply on each city the function evolveCity() which contain the sequence of action for each city
        // and return a list of tuple (city, exchange) which represent a new state at time t+1 for these cities
        val evolved =
          state.cities.map {
            city => evolveCity(city.id, state.cities, territory(city.id), state.date)
          }

        // 4 - take only the first object (cities) in the list "evolved"
        new State {
          val cities = evolved.map { case(city, _) => city }
          val date = state.date + 1
        }        
    }

    def ended(state: State) = {
      val maxInnov = maxInnovation(state.cities)
      val maxCity = maxCities(state.cities)

      // 3a - Break the simulation loop if one of these conditions is true
      state.date >= 4000 || /*maxPop > 70.0 || */ maxInnov > maxInnovation
    }

    // 1 - Launch the recursive simulation loop with initial loaded step, a the date 1
    val initialState: State = new State {
      val cities = loadedState
      val date = 1
    }

    Iterator.iterate(initialState)(simulationStep).takeWhile(s => !ended(s))
  }

When you compute each of this state (step of my simulation = 1 state) you need to store/write result anywhere, so i encapsulate run of simulation into a Writer class, here a 'CSVwriter'.

class CSVWriter(path: String, idExp:Int, seed:Long) {

  def apply(s: Simulation)(implicit aprng: Random) = {
    val writer = new BufferedWriter(new FileWriter(new File(path)))

    // TODO: Reader Nomad ? http://mergeconflict.com/reading-your-future/
    // Run stepWriter on each state, and write the result
    // Actually this code run the writer but don't return the final state ...

    try {
      writer.append("v_idn, v_exp, v_ticks, v_seed, v_pop" + "\n")
      s.states.foreach(stepWriter(_, writer))
    } finally writer.close
   }

  def stepWriter(dataToWrite: State, writer: Writer) = {
    writer.synchronized {
      val cities = dataToWrite.cities
      val year = dataToWrite.date
      cities.map {
        c => writer.append(List[Any](c.id.toInt, idExp.toInt, year.toInt, seed, c.population).map{_.toString}.mkString(",") + "\n")
      }
    }
  }

}

1 - As you can see, here, each step don't need the previous state, but in a really near future, this is the case : for example, to compute new exchange between our cities in our simulation a state T, we need to access to the exchanger created at the previous state T-1

2 - I need to return the last state to compute some score for my simulation, so i need the CSVWriter class write each state returned by s.states, and return also the last state computed.

3 I think it's possible to create a better and more generic solution for this problem : perhaps the reader monad can help me to create a better interface for this complex writer behaviour for state iterator but perhaps i mistake ?

I hope my question is more clear.

After some research i found the monad reader functionnal pattern, but if i understand globally the interest of this approach, i don't understand how i can translate the different example i read on the web ( like the first example here http://mergeconflict.com/reading-your-future/ ) to this simple problem. I try different code without success at this time :/

Do you have some simple explanation or pointers to help me understand the 'monad reader' ?

Or perhaps i totally mistake, and i can't resolve my problem with this approach ?

2

There are 2 answers

1
pagoda_5b On BEST ANSWER

Maybe you oversimplified your case, but if you need to process all the elements indipendently (meaning that the result of your operation on the ith-element is not needed for operation on the ith+1-element) you don't need a monad.

You can simply operate on all elements (even in parallel!), put the results back and get the last one

def doAndGetLast[A, B](input: List[A])(computation: A => B): B = 
    (input.par map computation).seq.last

val inputs = List(1,2,3,4)

def myMul(n: Int) = n * 2

val withInputs: (Int => Int) => Int = doAndGetLast(inputs) _

val result = withInputs(myMul) // or directly doAndGetLast(inputs)(myMul)
0
Régis Jean-Gilles On

You could simply (ab)use foldLeft:

val myDefaultValue = 0
l.foldLeft(myDefaultValue){ (_,x) => myPersonnalMultiply(x) }

where myDefaultValue is the value returned in the case where the list is empty.