Home > Software design >  Unexpected behaviour passing arguments to Scala Map.map
Unexpected behaviour passing arguments to Scala Map.map

Time:10-30

I have piece of code like this:

sealed trait Json
case class JsonNumber(number: Double)         extends Json
case class JsonString(text: String)           extends Json
case class JsonObject(obj: Map[String, Json]) extends Json

....and then

def trimAll(json: Json): Json = 
json match {
        case _: JsonNumber => json
        case JsonString(str) => JsonString(str.trim)
        case JsonObject(obj) =>
          val newObj = obj.map {
            case (key, value) => (key, trimAll(value))
          }
          JsonObject(newObj)
      }

Why does obj.map work here? When I see Map.map code, it's as:

def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = mapFactory.from(new View.Map(toIterable, f))

So it expects a function which takes input tuple (K,V) and returns another tuple. But in code above we are rather having:

{
   case (key, value) => (key, trimAll(value))
}

...and more surprisingly, if I do this:

val newObj = obj.map((k, v) => (k, trimAll(v)))

While this matches the signature of Map.map exactly, it gives me error:

Cannot resolve overloaded method 'map'

Edit: error trace is as:

[error] Note: The expected type requires a one-argument function accepting a 2-Tuple.
[error]       Consider a pattern matching anonymous function, `{ case (key, value) =>  ... }`
[error]           val newObj = obj.map((key,value) => (key, trimAll(value)))
[error]                                 ^
[error] .../JsonExercises.scala:18:37: missing parameter type
[error]           val newObj = obj.map((key,value) => (key, trimAll(value)))

This worked though:

val newObj = obj.map(kv => (kv._1, trimAll(kv._2)))

CodePudding user response:

I am assuming you are using Scala 2, as this was "resolved" in Scala 3.

case inside a map allows you to short hand pattern match the arguments, thus allowing you to explode the tuple into two named variables.

Let's take a closer look at the signature of map

def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = ...

Notice the extra parentheses around (K, V)

so map expects a unary function (function of one argument) that takes a tuple and returns another tuple.

So when you do

obj.map((k, v) => (k, trimAll(v)))

It is interpreted as a binary function, taking two arguments k and v and thus doesn't work.

Now you would think that you would be able to solve this as,

obj.map(((k, v)) => (k, trimAll(v)))

which unfortunately is a syntax that isn't supported in Scala 2. Tuples can not be deconstruction in the parameter in Scala 2.

As a work around, map( case ...)) syntax was developed.

Hope this clarifies it

CodePudding user response:

Short version: (k, v) => (k, trimAll(v)) is a function that takes two arguments, not a function that takes a single tuple argument.

  • Related