Why does the + operator on parameterized types in scala always result in a string

Consider the class as follows:

import scala.collection.mutable.{HashMap => MutableHashMap}
class CustomHashMap[K,V](hashMap: MutableHashMap[K,V], initVal: V) {
  def addMaps(first: MutableHashMap[K, V], second: MutableHashMap[K, V]): MutableHashMap[K, V] = {
    second.foreach(pair => { first += (pair._1 -> (first.getOrElse(pair._1, initVal) + pair._2)) } )
    //The above line throws a compile time error
    first
  }
  //Other functions
}

      

After adding two parameterized types, a compile-time error occurs saying

expected (K,V) recieved (K,String)

      

I want to know why scala is doing this implicit conversion? Since operator overloading is not allowed in java, it seemed logical, but in the case of scala, V could indeed be a class that could have a method for it +

.

+3


source to share


3 answers


Here's how to manually implement it:

Since you are trying to define a monoid, I will take the liberty of moving initVal

from addMaps

to an operation definition.

This can be done using the normal typeclass template in Scala, but you will need to define manually what it means +

for each type you want to use in your map.

Basically you have a trait Monoid

:

trait Monoid[T] {
  def mzero: T                        // your initVal
  def madd(first: T, second: T): T    // the + operation
}

      

Than you define implicit implementations for each type extending this trait. You can define them in

  • companion object Monoid

    , it will be used automatically,
  • a companion object of class T, it will also be used automatically,
  • as implicit somewhere else, but you'll have to import it manually.


Here is an example Monoid

of a companion object defining implementations for strings and all kinds of numbers:

object Monoid {
  implicit object StringMonoid extends Monoid[String] {
    def mzero = ""
    def madd(first: String, second: String) = first + second
  }

  implicit def NumericMonoid[T](implicit ev: Numeric[T]): Monoid[T] = 
    new Monoid[T] {
      import Numeric.Implicits._

      def mzero = ev.zero
      def madd(first: T, second: T) = first + second
    }
}

      

Then, in your function request, addMaps

specify that the map elements must be Monoid

s and use the operations provided by the monoid implementation for the elements:

def addMaps[K, V](first: MutableHashMap[K, V], second: MutableHashMap[K, V])
                 (implicit ev: Monoid[V]): MutableHashMap[K, V] = {
  second.foreach { pair => 
    first += (pair._1 -> ev.madd(first.getOrElse(pair._1, ev.mzero), pair._2)) }
  first
}

      

And here's a test of how it works:

scala> addMaps(MutableHashMap(1 -> 2, 3 -> 4), MutableHashMap(1 -> 3, 5 -> 7))
res1: scala.collection.mutable.HashMap[Int,Int] = Map(5 -> 7, 1 -> 5, 3 -> 4)

scala> addMaps(MutableHashMap(1 -> "foo", 2 -> "bar"), MutableHashMap(1 -> "baz", 3 -> "qoo"))
res2: scala.collection.mutable.HashMap[Int,String] = Map(2 -> bar, 1 -> foobaz, 3 -> qoo)

      

+2


source


I believe the problem is what you are trying to do V + V

and Scala infer the type V

as a string. Here is my attempt at telling the compiler what V

will extend the trait (called Adder

) that it will support V + V => V

.

import scala.collection.mutable.{HashMap => MutableHashMap}

trait Adder[A]{
    def +(b: A): A
}

class CustomHashMap[K, V <: Adder[V]](hashMap: MutableHashMap[K,V], initVal: V) {

  def addMaps(first: MutableHashMap[K, V], second: MutableHashMap[K, V]): MutableHashMap[K, V] = {
    second.foreach(pair => { first += (pair._1 -> (first.getOrElse[V](pair._1, initVal) + pair._2)) } )
    first
  }
  //Other functions ...
}

      



The above solution will require V

for extension Adder

. If we want to make it easier for the user, we can use implicit conversions. To add custom types, you need to cast the implicit conversion to scope (similar to what we did with Int, String, and Double).

import scala.collection.mutable.{HashMap => MutableHashMap}

trait Adder[A]{
  def add(x:A, y: A): A
}

implicit object IntAdder extends Adder[Int] {
  def add(x: Int, y: Int): Int = x + y
}

implicit object StringAdder extends Adder[String] {
  def add(x: String, y: String): String = x + y
}

implicit object DoubleAdder extends Adder[Double] {
  def add(x: Double, y: Double): Double = x + y
}

class CustomHashMap[K, V](hashMap: MutableHashMap[K,V], initVal: V)(implicit m: Adder[V]) {

  def addMaps(first: MutableHashMap[K, V], second: MutableHashMap[K, V]): MutableHashMap[K, V] = {
    second.foreach(pair => { first += (pair._1 -> m.add( first.getOrElse[V](pair._1, initVal), pair._2)) } )
    first
  }
  //Other functions ...
}

      

+2


source


To add to marios' answer, you can use a functional programming library that gives you class types already Semigroup

(padding) or Monoid

( padding and empty element).

For example, Cats :

scala> import cats.Monoid

scala> def joinMaps[K, V](a: Map[K, V], b: Map[K, V])(implicit m: Monoid[V]) =
  b.foldLeft(a) { case (res, (k, v)) => 
    res + (k -> m.combine(res.getOrElse(k, m.empty), v)) 
  }

      

To check:

scala> import cats.std.list._  // has a Monoid for List

scala> joinMaps(Map("a" -> List(1, 2), "b" -> List(3, 4)), 
                Map("b" -> List(5, 6), "c" -> List(7, 8)))
res1: Map[String,List[Int]] = Map(a -> List(1, 2), b -> List(3, 4, 5, 6),
                                  c -> List(7, 8))

      

In general, it makes your code more modular if you decouple the function to add the type to the class (here Monoid

), instead of requiring it to be part of your value type V <: Adder

.

+1


source







All Articles