Search code examples
scalaakkaakka-http

Scala Type Mismatch Error when creating a Map


I'm using scala 3.3 to build a map variable that temporarily stores a school's information like below.

Map's variable:


"staff" -> Set[CreateStaff]
"students" -> Set[CreateStudent]
"books" -> Set[CreateBook]

The map stores data inside heterogenous Set() parameters.

The data is generated using case classes CreateStaff, CreateStudent and CreateBook - traits of School.

And I try to store the data inside the map variable above using the code mapOS("staff")+=newStaffA which gives an error like below.

The same errors occur for the CreateStudent and CreateBook case classes , which I've omitted in the error text.

Error:


error: type mismatch;
found: Map[String,scala.collection.mutable.Set[_ >: this.CreateBook with this.CreateStudent with this.CreateStaff <: Product with this.School with java.io.Serializable]] (in scala.collection.mutable)
required: Map[String,Set[this.School]] (in scala.collection.immutable)
val mapOS: Map[String, Set[School]] = mutable.Map("staff" -> mutable.Set[CreateStaff](), "students" -> mutable.Set[CreateStudent](), "books" -> mutable.Set[CreateBook]())

error: value += is not a member of Set[this.School]
Expression does not convert to assignment because receiver is not assignable.
mapOS("staff")+=newStaffA

... OMITTED SIMILAR ERRORS FOR CreateStudent and CreateBook case classes
...             

My current code looks like below.

Could you help me identify and fix the errors!

Thanks in advance for your help!

school.scala:



import scala.collection.immutable
import scala.collection.mutable

sealed trait School

final case class CreateStaff(id: String, name: String) extends School
final case class CreateStudent(id: String, name: String) extends School
final case class CreateBook(id: String, name: String) extends School

val mapOS: Map[String, Set[School]] = mutable.Map("staff" -> mutable.Set[CreateStaff](), "students" -> mutable.Set[CreateStudent](), "books" -> mutable.Set[CreateBook]())

val newStaffA: CreateStaff = CreateStaff("id1", "name1")
val newStaffB: CreateStaff = CreateStaff("id1", "name1")

val newStudentA: CreateStudent = CreateStudent("id1", "name1")
val newStudentB: CreateStudent = CreateStudent("id1", "name1")

val newBookA: CreateBook = CreateBook("id1", "name1")
val newBookB: CreateBook = CreateBook("id1", "name1")


mapOS("staff")+=newStaffA
mapOS("staff")+=newStaffB

mapOS("students")+=newStudentA
mapOS("students")+=newStudentB

mapOS("books")+=newBookA
mapOS("books")+=newBookB

println(mapOS.getOrElse("staff", Set.empty))


Solution

  • To build on a comment I had with regard to have a class instead of a map, here is an example of how this would look like if it was modeled as such:

    final case class CreateStaff(id: String, name: String)
    final case class CreateStudent(id: String, name: String)
    final case class CreateBook(id: String, name: String)
    
    final case class School(
      staff: Set[CreateStaff] = Set.empty[CreateStaff],
      students: Set[CreateStudent] = Set.empty[CreateStudent],
      books: Set[CreateBook] = Set.empty[CreateBook],
    )
    
    val school = School()
    
    val staff = CreateStaff("id1", "name1")
    val student = CreateStudent("id1", "name1")
    val book = CreateBook("id1", "name1")
    
    val updatedSchool = school.copy(
      staff = school.staff + staff,
      students = school.students + student,
      books = school.books + book,
    )
    
    println(updatedSchool.staff) // prints "Set(CreateStaff(id1,name1))"
    

    This code is available for you to play around with here on Scastie.

    A few comments on the decisions I took:

    • Why proposing class? The code didn't really suggest that there was a good reason for this to be a map in the first place: the classes seemed relatively unrelated and bounded together by a common interface just to make sure that they can fit into a map.
      • Note that if this is a requirement you have, you can have a common trait across the different case classes, it's just that a class with more specific types this is not strictly necessary.
    • Why using immutable fields? In general, there's a tendency in Scala to favor immutability. Immutability makes it easier to reason about the effects of functions and test them, and remove a lot of possible bugs in concurrent code. You can read more about the difference between mutable and immutable collections here on the Scala documentation. If your use cases is such the mutability is a better choice, that's definitely a possibility.

    For completeness, this is the same example using mutable data structures instead:

    import scala.collection.mutable
    
    final case class CreateStaff(id: String, name: String)
    final case class CreateStudent(id: String, name: String)
    final case class CreateBook(id: String, name: String)
    
    final class School(
      val staff: mutable.Set[CreateStaff] = mutable.Set.empty[CreateStaff],
      val students: mutable.Set[CreateStudent] = mutable.Set.empty[CreateStudent],
      val books: mutable.Set[CreateBook] = mutable.Set.empty[CreateBook],
    )
    
    val school = new School()
    
    val staff = CreateStaff("id1", "name1")
    val student = CreateStudent("id1", "name1")
    val book = CreateBook("id1", "name1")
    
    school.staff += staff
    school.students += student
    school.books += book
    
    println(school.staff) // prints "HashSet(CreateStaff(id1,name1))"
    

    You can play around with this code here on Scastie.