Friday, October 16, 2015

Scala: Functions can be Keys of Maps

This came up in a discussion we had recently about what could be a key for a map in various languages. I jokingly suggested that we should take it to the extreme and have a function as a key for the map. On second thought, there was no reason we shouldn't be able to do this in Scala.

And it turns out it's possible with some limitations. I'm not sure why you'd do this. My only use case was to do with dependency injection. If you have a function what accepts a function, there might be a case where you'd like to know what implementation you were given. In most case you would not (and probably should not) care: that's the whole reason for injecting the implementation. Having said that, you may want it for logging purposes so you can tell what implementation was used.

Anyway, the use case doesn't really matter: this was all about can you, not would you.

So here's the REPL output of my little experiment. The big limitation here is that the function needs to be assigned to a val for this to be useful, and then that val must be used whenever the function is invoked. This seems intuitive to me: without having the actual function implementation, we can't tell at runtime whether an anonymous function or partially applied function is identical to a function assigned to a val.

scala> def add(a: Int, b:Int) = a + b
add: (a: Int, b: Int)Int

scala> val addFunc = add _
addFunc: (Int, Int) => Int = 

scala> def sub(a: Int, b: Int) = a - b
sub: (a: Int, b: Int)Int

scala> val subFunc = sub _
subFunc: (Int, Int) => Int = 

scala> val funcMap = Map(addFunc -> "add", subFunc -> "sub")
funcMap: scala.collection.immutable.Map[(Int, Int) => Int,String] = Map( -> add,  -> sub)

scala> def operation(a: Int, b: Int, op: (Int, Int) => Int, opMap: Map[(Int,Int) => Int, String]) = {
     |   val opString = opMap.getOrElse(op, "Unknown Function")
     |   println("Function: " + opString + " a: " + a + " b: " + b)
     |   op(a, b)
     | }
operation: (a: Int, b: Int, op: (Int, Int) => Int, opMap: Map[(Int, Int) => Int,String])Int

scala> operation(1, 2, addFunc, funcMap)
Function: add a: 1 b: 2
res0: Int = 3

scala> operation(1, 2, subFunc, funcMap)
Function: sub a: 1 b: 2
res1: Int = -1

scala> operation(1, 2, (a: Int, b: Int) => { a * b }, funcMap)
Function: Unknown Function a: 1 b: 2
res2: Int = 2

scala> operation(1, 2, add _, funcMap)
Function: Unknown Function a: 1 b: 2
res3: Int = 3