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

I need a generic function called "double" that behaves like this and can be applied to any type using the method def +(x:T):T

:

double("A")
> "AA"
double(1)
> 2
double(0.2)
> 0.4

      

So, I write this function like this:

def double[T](x:T):T = { x+x }

      

But when I run it in the REPL, scala says this:

scala> def double[T](x:T):T = { x+x }
<console>:7: error: type mismatch;
 found   : T
 required: String
       def double[T](x:T):T = { x+x }
                                  ^

      

I think structured type might be an approach to implement duck typing, and I've tried something like this, but it doesn't work either:

def double[T <: { def +(x:T):T }](x:T):T = { x + x }
def double[T <: { def +[U<:T](x:U):U}](x:T) = { x + x }

      

Does anyone have any ideas about this? Thank!


I found in Haskell, a similar function can be written like this:

double x = x + x

      

just wondering why I can't do this in Scala ...

+3


source to share


3 answers


Not every type T

has a method +

, so it cannot work. The strange error message comes from the compiler treating the first x

like String

because every type has a method toString

and that the only way to see the general T

as having +

. But then it T

is passed +

instead String

, and that requires a second implicit conversion, and if we did, it would return String

instead T

.

The problem is that we need a way to provide evidence that it T

has an operation +

. As far as I know, there is nothing in the standard library, but we can create a type class that will provide 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)
    }
}

      

We can now define a method double

that requires proof of type 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 should put all the class instance type, such as StringDouble

, and numericDouble

in the companion object CanDouble

.


I don't think a structured type can work at all here, because you are not allowed to use an abstract type parameter in a structural qualifier that is defined outside of the trait (type parameter T

). From SLS :

In a method declaration in a structural qualifier, the type of any value parameter can only refer to type parameters or abstract types that are contained within the qualifier. That is, it must refer either to the type parameter of the method itself, or to a type inference within the qualifier. This limitation does not apply to the result type of the method.

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

+8


source


In haskell, you can:

Prelude> let double x = x + x // (1)
Prelude> let quadruple x = double (double x) //(2)

Prelude> :t double
double :: Num a => a -> a
Prelude> :t quadruple
quadruple :: Num a => a -> a

      

In scala, you need to explicitly specify Num

scala> def double[T: Numeric] (a: T) = implicitly[Numeric[T]].plus(a, a)
double: [T](a: T)(implicit evidence$1: Numeric[T])T

scala> def quadruple[T: Numeric](a: T) = double(double(a))
quadruple: [T](a: T)(implicit evidence$1: Numeric[T])T

      

Because haskell type inferrence is smarter. (1) string st found typeclass Num

:

Prelude> :info Num
class Num a where
  (+) :: a -> a -> a //looks like structural types, but ...
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in ‘GHC.Num’ //... but here is implementations found accross build - they are explicitly saying that they are instances of Num

instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

      

Also, scala has problems with structure types - you can't define a polymorphic structure type (not only that - you don't define polymorphic lambdas) "The type of a parameter in a structural rework may not refer to an abstract type defined outside of this trait."

Otherwise it Num

will be defined in scala something like this:



implicit class Num[T <: { def +(x:T):T }](a: T) = ... //will not work, and pretty slow by the way

      

See other answers to see how this is defined ( Numeric

).

On (2) the nth line, the compiler enters the input type for x

( Num x

) from the application double

. scala just can't do it. Its analogue haskell Num

would be:

scala> trait Num[T]{ val a: T; def + (b: Num[T]): Num[T] }
defined trait Num

scala> implicit class NumInt(val a: Int) extends Num[Int] {override def + (b: Num[Int]) = NumInt(a + b.a)}
defined class NumInt

scala> def double[T](a: Num[T]) = a + a
double: [T](a: Num[T])Num[T]

scala> double(5)
res4: Num[Int] = NumInt@424f5762

      

But the problem is still the same - you need to specify the input types ( a: Num[T]

) in scala, this cannot deduce them.

However, even in Haskell, you cannot say:

Prelude> let double x = x +++ x

<interactive>:28:18:
    Not in scope: ‘+++’
    Perhaps you meant ‘++’ (imported from Prelude)
Otherwise `Num` would be defined in Scala as something like that:

      

And using Haskell in real duck is not that easy: http://chrisdone.com/posts/duck-typing-in-haskell

+3


source


This is a great example of when to use type classes.

+

is just a function. You have not provided the compiler with any information such as

def +(t : T, t : T) : T = ...

      

And you couldn't, because you don't know what T.

Here it will work like this. You have a type constructor called Doubles:

trait Doubles[T]{
  def double(t : T) : T
}

      

Now, in the companion object, just for convenience, I rewrote your double function as follows:

object Doubles{
  def double[T](t : T)(implicit doubles : Doubles[T]) =    doubles.double(t)
}

      

So this suggests that I can double T as long as you have Doubles for T, or explicitly provided me with Doubles for T. Otherwise, I cannot double T and you'll get a compiler error.

Instances of this class are listed below: Doubles [T]:

object Implicits{
    //Now, you wouldn't want to have to write this for 
    //every kind of number.  Scala already provides you with a numeric
    //typeclass.  So here a function that gives a Doubles[N] 
    //whenever you ask for a Doubles[Numeric[T]], i.e. a Doubles for a     
    //member of the Numeric typeclass:

    implicit def numDoubler[N](implicit num : Numeric[N]) : Doubles[N] = new Doubles[N]{
        def double(n : N) : N = num.plus(n,n)
    }

    implicit object stringDoubler extends Doubles[String]{
        def double(t : String) : String = t + t
    }

 //So something like this is no longer needed:
 // implicit object intDoubler extends Doubles[Int]{
 //   def double(t : Int) : Int = t + t
 // }

}

      

+1


source







All Articles