首页 文章

为什么scala中参数化类型的运算符总是导致字符串

提问于
浏览
3

考虑如下课程:

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
}

添加两个参数化类型后,会出现编译时错误

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

我想知道为什么scala会进行隐式转换?由于java中不允许运算符重载,这似乎是合乎逻辑的,但是在scala的情况下,V实际上可能是一个可以为它定义方法 + 的类 .

3 回答

  • 2

    这是一种如何手动实现这种事情的方法:

    因为看起来你正试图定义一个幺半群,我会冒昧地将 initValaddMaps 移动到操作的定义 .

    这可以使用Scala中的常见类型类模式来完成,但您必须手动定义 + 对于您希望在 Map 中使用的每种类型的含义 .

    基本上你有一个特点 Monoid

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

    比为扩展该特征的每种类型定义隐式实现 . 你可以在中定义它们

    • Monoid 的伴侣对象,它将自动使用,

    • 类T的伴侣对象,它也会自动使用,

    • 作为隐含的其他地方,但您必须手动导入它 .

    以下是定义字符串和各种数字实现的 Monoid 伴随对象的示例:

    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
        }
    }
    

    然后在你的 addMaps 函数请求中, Map 的元素应该是 Monoid ,并使用元素上的monoid实现提供的操作:

    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
    }
    

    以下是测试工作原理:

    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

    我相信问题是因为你试图做 V + V 而Scala推断类型 V 是一个字符串 . 这是我尝试向编译器指定 V 将扩展一个支持 V + V => V 的特征(称为 Adder ) .

    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 ...
    }
    

    上述解决方案将需要 V 来扩展 Adder . 如果我们想让用户更容易,我们可以使用隐式转换 . 为了添加自定义类型,您需要对范围进行隐式转换(类似于我们对Int,String和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 ...
    }
    
  • 1

    只是为了添加marios的答案,你可能想要使用一个函数式编程库,它已经为你提供了类型类 Semigroup (加法)或 Monoid (加法和空元素) .

    例如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)) 
      }
    

    去测试:

    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))
    

    通常,如果将加法函数分离为类型类(此处为 Monoid )而不是要求它成为值类型 V <: Adder 的一部分,则会使代码更加模块化 .

相关问题