Home > Enterprise >  Why doesn't "foldLeft" work with class members in Scala?
Why doesn't "foldLeft" work with class members in Scala?

Time:11-09

I am trying to remove classes with identical fields from a collection using foldLeft function in Scala.

For example, a Box class is defined as: class Box(color: String = "", size: Int = 0)

Let's create a simple collection Seq(Box, Box, Box) and try to filter it by the size field.

val s = Seq(Box("green", 1), Box("blue", 1), Box("red", 3))

s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
  if (boxes.last.size != nextBox.size) { // throws an exception
    boxes :  nextBox
  } else {
    boxes
  }
}

The above code throws an exception: value size cannot be accessed as a member of Box. The above code works fine with primitive types and for Seq(1, 1, 3) will produce 1, 3. It doesn't help if you explicitly tell the compiler (boxes: Seq[Box], nextBox: Box), it doesn't help even if you cast the value returned by last to Box.

Can you please explain why?

CodePudding user response:

The above code throws an exception: value size cannot be accessed as a member of Box.

Because Box doesn't have any public field called size; it has a constructor parameter called size which is different.
You can fix that doing class Box(color: String = "", val size: Int = 0) to make the constructor argument a field; or by using a case class that does that for you (and much more).

PS: It doesn't throw an exception, it fails with a compiler error; two very different things.


Also, what you are trying to do is just distinctBy; as I always say the Scaladoc is your friend.

final case class Box(color: String = "", size: Int = 0)

val result = boxes.distinctBy(_.size)

Since you seem very new to the language and with many basic errors / misconceptions I would advise you to pick some course or read a book about the language. Also, I would encourage you to join the Scala Discord server where you may ask questions and get more interactive help.

CodePudding user response:

Some code must be missing from your description, since you can't call Box(...) if Box is just a class. You could if it were a case class and then size would be accessible too, since a case class exposes all it's constructor arguments as members, whereas a standard class does not unless explicitly annotated with val:

scala> case class Box(color: String = "", size: Int = 0)
class Box

scala> val s = Seq(Box("green", 1), Box("green", 1), Box("green", 3))
val s: Seq[Box] = List(Box(green,1), Box(green,1), Box(green,3))

scala> s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
     |   if (boxes.last.size != nextBox.size) {
     |     boxes :  nextBox
     |   } else {
     |     boxes
     |   }
     | }
val res0: Seq[Box] = List(Box(green,1), Box(green,3))

Alternatively (notice the new of val in the class constructor and new to instantiate them):

scala> class Box(val color: String = "", val size: Int = 0)
class Box

scala> val s = Seq(new Box("green", 1), new Box("green", 1), new Box("green", 3))
val s: Seq[Box] = List(Box@159424e2, Box@29bcf51d, Box@1e54a6b1)

scala> s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
     |   if (boxes.last.size != nextBox.size) {
     |     boxes :  nextBox
     |   } else {
     |     boxes
     |   }
     | }
val res0: Seq[Box] = List(Box@159424e2, Box@1e54a6b1)
  • Related