8

I'm trying to create a class that has a map of keys -> function calls, and the following code is not behaving as I would like it to.

class MyClass {
    val rnd = scala.util.Random

    def method1():Double = {
        rnd.nextDouble
    }

    def method2():Double = {
        rnd.nextDouble
    }

    def method3():Double = {
        rnd.nextDouble
    }

    def method4():Double = {
        rnd.nextDouble
    }

    def method5():Double = {
        rnd.nextDouble
    }

    var m = Map[String,Double]()    
    m += {"key1"-> method1}
    m += {"key2"-> method2}
    m += {"key3"-> method3}
    m += {"key4"-> method4}
    m += {"key5"-> method5}

    def computeValues(keyList:List[String]):Map[String,Double] = {
        var map = Map[String,Double]()
        keyList.foreach(factor => {
            val value = m(factor)
            map += {factor -> value}
        })
        map
    }

}

object Test {
    def main(args : Array[String]) {
        val b = new MyClass
        for(i<-0 until 3) {
            val computedValues = b.computeValues(List("key1","key4"))
            computedValues.foreach(element => println(element._2))
        }
    }
}

The following output

0.022303440910331762
0.8557634244639081
0.022303440910331762
0.8557634244639081
0.022303440910331762
0.8557634244639081

indicates that once the function is placed in the map, it's a frozen instance (each key producing the same value for each pass). The behavior I would like to see is that the key would refer to a function call, generating a new random value rather than just returning the instance held in the map.

Bruce Ferguson
  • 1,851
  • 2
  • 16
  • 21

3 Answers3

18

The problem is with the signature of your map m. You described that you wanted to put functions in the map; however you've declared it as Map[String, Double] which is just a map of strings to doubles. The correct type would be Map[String, () => Double].

Because brackets are optional on no-arg method invocations, the difference in types here is very important. When the map is being populated, the methods are invoked at insertion time in order to match the type signature (I believe this is done by an implicit conversion but I'm not 100% sure).

By simply changing the declared signature of your map, the functions are inserted as you desire, and can be evaluated during computeValues (requires a change on line 35 to map += {factor -> value()}), resulting in the following output (tested on Scala 2.8):

0.662682479130198
0.5106611727782306
0.6939805749938253
0.763581022199048
0.8785861039613938
0.9310533868752249
Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • The methods are really invoked at insertion time so they return Double, but since zero-method arguments can be called without parenthesis, there was no need of implicit conversion for that purpose. See http://stackoverflow.com/q/6643030/1287856 – Mikaël Mayer May 24 '13 at 14:49
5

You need to map the keys to functions, not to the answer that the function would give you. Try this:

var m = Map[String,() => Double]()
m += /* etc. */
m.values.foreach(println(x => x()))
m.values.foreach(println(x => x()))
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
0

I would use scala's type inference to define the map. There's no need to define the methods separately, if they're only used via the map. Also you can use an immutable val, instead of a mutable var.

val m = Map( "key1" -> {() => rnd.nextDouble},
  "key2" -> {() => rnd.nextDouble},
  "key3" -> {() => rnd.nextDouble},
  "key4" -> {() => rnd.nextDouble},
  "key5" -> {() => rnd.nextDouble})

You also need to change line 35 to value()

naedyr
  • 21
  • 4
  • Thanks, the functions DO need to be defined separately. They are merely placeholders for actual functions that I'm going to define later. I just used random numbers to confirm correct behavior. – Bruce Ferguson Feb 15 '11 at 12:22