cogitate
cogitate

Reputation: 77

scala parameterized type on foldLeft

given the following signature for a parameterized method

def double[A <: Byte](in:List[A]): List[A] = {
  //double the values of the list using foldLeft
  //for ex. something like:
  in.foldLeft(List[A]())((r,c) => (2*c) :: r).reverse
  //but it doesn't work! so.. 
}

i was trying to get the following before tackling parameterized typed foldLeft

def plainDouble[Int](in:List[Int]): List[Int] = {
  in.foldLeft(List[Int]())((r:List[Int], c:Int) => {
   var k = 2*c
   println("r is ["+r+"], c is ["+c+"]")
   //want to prepend to list r
   // k :: r 
   r
})
} 

however, this results in the following error:

$scala fold_ex.scala
error: overloaded method value * with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: scala.Int)scala.Int <and>
(x: Char)scala.Int <and>
(x: Short)scala.Int <and>
(x: Byte)scala.Int
cannot be applied to (Int(in method plainDouble))
val k = 2*c
         ^
one error found

if i changed the signature of the def to the following:

def plainDouble(in:List[Int]): List[Int] = { ...}

works and the output for :

val in = List(1,2,3,4,5)
println("in "+ in + " plainDouble ["+plainDouble(in)+"]")

is

in List(1, 2, 3, 4, 5) plainDouble [List(2, 4, 6, 8, 10)]

apologies if i am missing something very obvious.

Upvotes: 2

Views: 808

Answers (2)

Michael Zajac
Michael Zajac

Reputation: 55569

@DNA is correct in that plainDouble[Int] declares a type parameter named Int, that has nothing to do with the actual type. So your attempt to make it non-generic is actually still generic, but in a way that is not quickly apparent.

But what about the original problem?

scala> def double[A <: Byte](in: List[A]): List[A] = in.foldLeft(List.empty[A])((r,c) => (2*c) :: r)
<console>:15: error: type mismatch;
 found   : x$1.type (with underlying type Int)
 required: A
       def double[A <: Byte](in: List[A]): List[A] = in.foldLeft(List.empty[A])((r,c) => (2*c) :: r).reverse
                                                                                               ^

The problem here is that 2 * c is an Int, and not an A. The *(byte: Byte) method on Int returns another Int. Hence the message (with underlying type Int). Notice that if you cast to A, it compiles:

def double[A <: Byte](in: List[A]): List[A] =
    in.foldLeft(List.empty[A])((r,c) => (2*c).toByte.asInstanceOf[A] :: r).reverse

Notice how I also had to call toByte before casting to A. This isn't exactly a shining example of generics at work, but the point is that incompatible return types are causing the error.

Also notice how it doesn't occur if you remove 2 *:

def double[A <: Byte](in: List[A]): List[A] =
    in.foldLeft(List.empty[A])((r,c) => c :: r).reverse

Edit:

You might consider using the Numeric trait for generics like this.

import scala.math.Numeric.Implicits._

def double[A: Numeric](in: List[A])(implicit i2a: Int => A): List[A] =
    in.map(_ * 2)

This relies on an implicit Numeric[A] being available for your numeric type (which there are in the scala.math.Numeric object, for pretty much any numeric type you will want). It also relies on an implicit conversion being available from Int to A, so that we can write a * 2. We can drop this constraint by using + instead:

def double[A: Numeric](in: List[A]): List[A] = in.map(a => a + a)

Upvotes: 1

DNA
DNA

Reputation: 42586

The problem is a kind of name shadowing:

def plainDouble[Int](in:List[Int]): List[Int] = {
                ^^^
      // this is a type parameter called "Int"

You are declaring a type variable called Int, whilst also trying to use the concrete type Int, and this causes the confusion. If you remove the type variable (since it's not actually used) or rename it to I, for example, then the code compiles.

Upvotes: 3

Related Questions