In Scala, why can't I implement a trivial generic function like this?

前端 未结 3 1390
太阳男子
太阳男子 2021-01-20 03:30

I want a generic function called \"double\", which behaves like this and could be applied to any type with def +(x:T):T method:

double(\"A\")
&g         


        
3条回答
  •  日久生厌
    2021-01-20 04:31

    Not every type T has a + method, so that can't work. The strange error message comes from the compiler treating the first x as a String, because every type has a toString method, and that's the only way it can see a generic T as having +. But then T is being passed to +, instead of String, and it would require a second implicit conversion to allow this to work--and even if we did that, it would return String instead of T.

    The problem is that we need a way to provide evidence that T has a + operation. There isn't anything in the standard library that does exactly this, to my knowledge, but we can create a type class that would provide the evidence that a type can be "doubled".

    trait CanDouble[A] {
        def double(a: A): A
    }
    
    // Create some instances for types we know, like String, or numeric types
    implicit val StringDouble: CanDouble[String] = new CanDouble[String] {
        def double(a: String): String = a + a
    }
    
    // Uses the Numeric type class to create a CanDouble for all Numeric types
    implicit def numericDouble[A: Numeric]: CanDouble[A] = {
        new CanDouble[A] {
             def double(a: A): A = implicitly[Numeric[A]].plus(a, a)
        }
    }
    

    Now we can define the double method that requires a evidence of the type class CanDouble.

    def double[A: CanDouble](a: A): A = implicitly[CanDouble[A]].double(a)
    
    scala> double(1)
    res4: Int = 2
    
    scala> double(0.4)
    res5: Double = 0.8
    
    scala> double("a")
    res6: String = aa
    

    Ideally, you would put all of the type class instances like StringDouble and numericDouble within the companion object CanDouble.


    I don't think a structural type can work at all here, because you are not allowed to use an abstract type parameter within the structural refinement that is defined outside of the refinement (the type parameter T). From the SLS:

    Within a method declaration in a structural refinement, the type of any value parameter may only refer to type parameters or abstract types that are contained inside the refinement. That is, it must refer either to a type parameter of the method itself, or to a type definition within the refinement. This restriction does not apply to the method's result type.

    Structural types should typically be avoided, anyway, as they are quite slow. Type classes should be preferred in this scenario.

提交回复
热议问题