user48956
user48956

Reputation: 15800

scala Map.getOrElse - how to provide function for default

Odd...

    val h = new HashMap[Long, Int]()

    def mydefault0():Int = 101
    println( h.getOrElse(99, default=mydefault0 _ ) )  // Prints <function0>

    def mydefault1(key:Long):Int = 102
    println( h.getOrElse(98, default=mydefault1 _ ) )  // Prints <function1>

The docs say that default must be of type: => B

If I understand correctly, a no-arg function returning an Int, in this case.

  1. Why does the example taking mydefault1 compile, as it takes an argument and so does meet the specification?

  2. Why are functions returned, instead of functions being called to yield a default value? Apparently the typesafety has been broken because the getOrElse must return an Int, not a function. (if I misunderstood the docs and incorrectly provided a function where an Int value was required, why did the compiler let me provide a function, instead of an Int?).

edit

Clearly:

also let a function by used to specify a default value. What I'd like is to be able to override the default with a function that's provided at the point of performing the lookup (i.e. the function may change during the life of the Map)

Is this possible?

Upvotes: 7

Views: 10559

Answers (1)

0__
0__

Reputation: 67310

The definition of getOrElse:

getOrElse[B1 >: B](key: A, default: => B1): B1

The default argument does not take a function—which would be () => B1, but a lazily accessed value of type B1. This parameterless "function" => B1 is also sometimes called thunk. The correct way to use it is as follows:

import collection.mutable

val h = new mutable.HashMap[Long, Int]()

def mydefault0(): Int = 101

println(h.getOrElse(99, default = mydefault0()))

So what is it that you are seeing with mydefault0 _? Clearly, the returned value has type B1 which must be a common supertype of the map's value type Int and the default value's type. The default value's type is Function0. If you assign the result, you see that the supertype is Any:

val x = h.getOrElse(99, default = mydefault0 _ )  // x: Any = <function0>

So the mistake is to assume that you must pass in a function, when actually you are stating an expression which is lazily evaluated. The call mydefault0() will only be invoked when the default value is needed. Formally, the argument is defined as a call-by-name argument.


Edit: Regarding your comment. Call-by-name means that you do get a new array each time.

val m = Map("foo" -> Array(1, 2, 3))

def myDefault = {
  println("called-default")
  Array(4, 5, 6)
}

val a1 = m.getOrElse("foo", myDefault)  // myDefault not called
val a2 = m.getOrElse("bar", myDefault)  // myDefault called
val a3 = m.getOrElse("baz", myDefault)  // myDefault called
a2 == a3  // false!!

Upvotes: 17

Related Questions