2

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.

cogitate
  • 77
  • 7

2 Answers2

3

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.

DNA
  • 42,007
  • 12
  • 107
  • 146
1

@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)
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • thank you "DNA" and @m-z however, `def doubleBound[A <: Byte](in: List[A]): List[A] = in.foldLeft(List.empty[A])((r,c) => (2*c).toByte.asInstanceOf[A] :: r).reverse val in = List(1,2,3,4,5) println("in "+ in + " doubleBound ["+doubleBound(in)+"]")` still complains with `error: inferred type arguments [Int] do not conform to method doubleBound's type parameter bounds [A <: Byte]` – cogitate Apr 01 '15 at 17:48
  • @cogitate An `Int` is not `Byte`. `A` cannot represent an `Int`, if `A` is bounded above by `Byte`. – Michael Zajac Apr 01 '15 at 17:53
  • Try using an actual list of `Byte`s: `List(1, 2, 3). map(_.toByte)` – Michael Zajac Apr 01 '15 at 17:54
  • agree!! so what should a generic double that takes a list of numbers look like? – cogitate Apr 01 '15 at 18:12
  • @cogitate See my edit. The issue is really that the numeric types in scala do not extend a common type other than `AnyVal`. However, there is the `Numeric` type class, which you may be interested in. – Michael Zajac Apr 01 '15 at 18:22
  • @m-v thank you for spending so much time on this. grateful. i will mark your answer as "correct". i just drank too much kool-aid that in scala i can really write generic interfaces and handle the types. foldLeft is what i am learning slowly and haven't yet understood how to use implicits.thank you. – cogitate Apr 01 '15 at 21:23