1

I'm following the excellent Functional Programming in Scala by Paul Chiusano and Rúnar Bjarnason, and had a question to what I find is odd/unexpected behaviour.

I have defined a foldRight function like so

def foldRight[A,B](l: List[A], z: B)(f: (A,B) => B): B = 
  l match {
    case Nil => z
    case x :: xs => f(x, foldRight(xs, z)(f)) 
  }

that works fine as long I pass in concrete arguments, e.g.:

foldRight((1 to 10).toList, 0)(_ + _)
val res6: Int = 55

If I define a similar function that takes the generic list

def sum[A](as: List[A]): Int = foldRight(as, 0)(_ + _)

something odd happens

  def sum[A](as: List[A]): Int = foldRight(as, 0)(_ + _)
                                                      ^
fold.scala:23: error: type mismatch;
 found   : Int
 required: String

Initially, I was super puzzled by this error message given the only types in play are A, B and Int. However, would seem it simply attempts to make sense of + and generic A's by calling .toString on them as I read about here.

If that is the case though, why come it doesn't simply adds a1.toString + a2.toString + ... + an.toString + 0 = something0? String concatenation between String and Int in Scala is fully understood by the compiler.

Hope someone can help clarifying what's happening here.

7
  • 3
    How do you expect to sum a list of generic A? What if I give you a List of Maps of Users? That should give you an idea of what is wrong. Now, for the specific error message (_ + _) is expanded as (x: A, y: Int) => x + y, since we do not know anything of A all we can assume is that is has the same methods of Any, which does has a + method (due freaking Java) which also takes an Any and returns an String. So x + y gives an String, but the compiler expected a function from (A, Int) => Int, thus the error. Commented May 10, 2020 at 16:59
  • 2
    At this moment, you simply can not write that function. Latter, you will learn about typclasses and you will be able to write this function in terms of any A that behaves as a Number. Commented May 10, 2020 at 17:00
  • Super helpful and clarifying answer, thank you, Luis! Commented May 10, 2020 at 17:06
  • 2
    look at scala-lang.org/api/current/scala/Any.html and filter members by inheritance to find the + method Commented May 10, 2020 at 17:08
  • 1
    @Saskia Technically speaking no. Practically speaking yes, because anything has to have all the method of Any (but they may throw exceptions, like for null, or you may never get an actual value to call the methods, like with Nothing). Also, I believe there is an implicit conversion from A to Any always in scope. Commented May 10, 2020 at 22:07

3 Answers 3

1

However, would seem it simply attempts to make sense of + and generic A's by calling .toString

This is not what happens. The result is similar, but it's not calling .toString. You found the underlying problem though.

The sum function operates on a generic A on which you want to call the +method on.

But A doesn't have a + method (Remember that + in infix position is the same as x.+(y)). The compiler then searches the implicit scope for a function or class constructor to convert this A into something that has a + method. It finds it in any2stringadd.

Your method actually looks like

def sum[A](as: List[A]): Int = foldRight(as, 0)(any2Stringadd(_) + _)

Now the error makes sense. Because the + method of the any2Stringadd class expects a string as its argument. But your z argument is of type Int. You can see that, when you explicitly add the types to the inline function argument.

As other pointed out, this is not reconcilable without constraining the type parameter.

Sign up to request clarification or add additional context in comments.

Comments

0

As others have said, you can't try to add instances of some type A that you know nothing about, since the Scala compiler gets confused and tries to implicitly turn your A objects into Strings, even though you have 0, which is an Int. However, you can get around this with typeclasses, like this.

trait Sum[T] {
  def id: T
  def add(_1: T, _2: T): T
}

implicit object IntSum extends Sum[Int] {
  def id = 0
  def add(i1: Int, i2: Int) = i1 + i2
}

def sum[A](as: List[A])(implicit sum: Sum[A]): A = foldRight(as, sum.id)(sum.add)

Here, IntSum is passed to the sum method implicitly, and its add method is used to sum your list together. You can define as many such implicit objects as you like for Double, String, etc.

Comments

0

Let's name some functions properly by thinking about what they are supposed to do:

def foldRight[A,B](l: List[A], z: B)(f: (A,B) => B): B =
  l match {
    case Nil => z
    case x :: xs => f(x, foldRight(xs, z)(f))
  }

def sumInts(numbers: List[Int]): Int = foldRight(numbers, 0)(_ + _)

def concatStrings(strings: List[String]): String = foldRight(strings, "")(_ + _)

def concatStringRepresentations[A](as: List[A]): String = foldRight(as, "")(_ + _)
concatStringRepresentations(List(List(), List(1,2,3), List(0))) // List()List(1, 2, 3)List(0)
concatStringRepresentations(List(1.0, 2.0, 3.0)) // 1.02.03.0

def sumIntRepresentations[A](as: List[A]): Int = foldRight(as, 0)(_ + _)  
// --> type mismatch, found: String, required: Int, for second param of (_ + _)

This is what your sum function was probably supposed to be named. To make it compile, we wish that there is

  • a method +: Int => Int defined for any type, or...
  • implicit conversions that convert any type to Int (something like toString with Int), or...
  • other implicit conversions that make sense, or...
  • something else, that can make this function succeed and is recognized by the compiler.

It turns out that Any can be implicitly converted to something that has a method +(other: String) => String that makes the String example work, but there is no implicit conversion from Any to something that has a method +(other: Int) => Int.

This could be explained in more detail and other answers already do that.

Opinions:

So, yes, the compiler gives a message that is confusing due to existence of implicit conversions. But let's look at the problem at hand which probably was our wishful thinking. Sometimes stuff just works like in the concatStringRepresentations function. It does not work in general, but in this special case with String it does.

Sometimes something works for a special case. And when we don't see that it is a special case, mistakes can happen, for instance when we apply it to something else.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.