diff --git a/src/Ex2.scala b/src/Ex2.scala
index 327e80d0625def02b1ec5880d5cb410a9801527c..72ccb3fe117191d8324d2b4e323bdd61e31abbc9 100644
--- a/src/Ex2.scala
+++ b/src/Ex2.scala
@@ -1,3 +1,4 @@
+package ex2
 // Create a class model of the hospital including the following:
 // units
 // doctors
diff --git a/src/Ex4.scala b/src/Ex4.scala
new file mode 100644
index 0000000000000000000000000000000000000000..272f20b643d4947bb2560b149300924a9da30566
--- /dev/null
+++ b/src/Ex4.scala
@@ -0,0 +1,116 @@
+package ex4
+// Create a class model of the hospital including the following:
+// units
+// doctors
+// patients
+// Create methods relative to these classes e.g.
+// create consultation
+// reserve appointment
+// Include one use of Type Restriction, why is it useful?
+// Include one example of Covariance and one of Contravariance, why are they useful?
+
+enum Expertise {
+    case Cardiology
+    case Surgery
+    case Endocrinology
+    case Neurology
+    case Ophthalmology
+    case Pediatrics
+    case Psychiatry
+    case Urology
+}
+
+extension (s: String)
+    def isName: Boolean = s.matches("[A-Z][a-z]+")
+
+trait Person {
+    val name: String
+    var age: Int
+}
+
+class Doctor(val name: String, var age: Int, val expertises: List[Expertise])
+    extends Person {
+    def consult(patient: Patient)(using hospital: Hospital) =
+        println(s"Doctor $name is consulting patient ${patient.name}" +
+            s"with condition ${patient.condition}.")
+    given Conversion[Doctor, Patient] = (d: Doctor) => Patient("mental", d.name, d.age)
+}
+
+case class Patient(condition: String, val name: String, var age: Int)
+    extends Person {
+    def requestAppointment(using hospital: Hospital) = {
+        val unit =
+            hospital.units.find(u =>
+                u.areaOfExpertise == hospital.processCondition(this.condition)
+            )
+        if unit.isDefined then unit.get.addPatient(this)
+        else throw Exception("No unit available for this condition.")
+    }
+}
+
+// Covariance is useful here because a list of doctors is a list of persons
+// and we might want to use a list of doctors in a method that expects a list of persons
+// Type restriction is useful here because we want to restrict the type of the list to only Doctors
+case class UnitPersonnel[+T <: Doctor](list: List[T])
+
+class HospitalUnit(
+    var areaOfExpertise: Expertise,
+    var doctors: UnitPersonnel[Doctor],
+    var patients: List[Patient]
+) {
+    def assignDoctor(doctor: Doctor): Unit = doctors = UnitPersonnel(
+      doctors.list :+ doctor
+    )
+
+    def addPatient(patient: Patient)(using hospital: Hospital) = {
+        patients = patients :+ patient
+        doctors.list.last.consult(patient)
+    }
+}
+
+// Contravariance is useful here because we might create summaries of generic types
+abstract class SummaryPrinter[-T]:
+    def printSummary(value: T): String
+
+class Hospital {
+    var units: List[HospitalUnit] = List()
+    var doctors: List[Doctor] = List()
+    def processCondition(condition: String) = condition match {
+        case "heart"     => Expertise.Cardiology
+        case "brain"     => Expertise.Neurology
+        case "eye"       => Expertise.Ophthalmology
+        case "kidney"    => Expertise.Urology
+        case "bone"      => Expertise.Surgery
+        case "endocrine" => Expertise.Endocrinology
+        case "child"     => Expertise.Pediatrics
+        case "mental"    => Expertise.Psychiatry
+        case _           => Expertise.Surgery
+    }
+    def addDoctor(d: Doctor): Unit = {
+        if !d.name.isName then throw Exception("Invalid name.")
+        for expertise <- d.expertises do {
+            val unit = units.find(_.areaOfExpertise == expertise)
+            if unit.isDefined then {
+                doctors = doctors :+ d
+                unit.get.assignDoctor(d)
+                return
+            }
+        }
+        addUnit(d.expertises.head, d)
+    }
+    def addUnit(expertise: Expertise, doctor: Doctor) = {
+        val unit =
+            new HospitalUnit(
+              expertise,
+              UnitPersonnel[Doctor](List(doctor)),
+              List()
+            )
+        units = units :+ unit
+    }
+    def listDoctors = doctors.foreach(PersonSummaryPrinter.printSummary(_))
+
+    object PersonSummaryPrinter extends SummaryPrinter[Person]():
+        def printSummary(person: Person): String =
+            s"${person.name} is ${person.age} years old."
+
+}
diff --git a/test-src/Ex4Spec.scala b/test-src/Ex4Spec.scala
new file mode 100644
index 0000000000000000000000000000000000000000..ba2ecf03a6892398942ba09ea52cbbdc6981ffd4
--- /dev/null
+++ b/test-src/Ex4Spec.scala
@@ -0,0 +1,33 @@
+package ex4
+
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class Ex4Spec extends AnyWordSpec with Matchers {
+    "An hospital" should {
+        val hospital = new Hospital
+        given Hospital = hospital
+        "have a list of units" in {
+            hospital.units should be(List())
+        }
+        "have a list of doctors" in {
+            hospital.doctors should be(List())
+        }
+        "accept new doctors" in {
+            val doctor = new Doctor("John", 30, List(Expertise.Cardiology))
+            hospital.addDoctor(doctor)
+            hospital.units should have length 1
+        }
+        "accept appointments" in {
+            val patient = Patient("heart", "John", 30)
+            patient.requestAppointment
+            hospital.units.head.patients should have length 1
+        }
+        "throw an exception if doctor name is invalid" in {
+            val doctor = new Doctor("23864", 30, List(Expertise.Cardiology))
+            an[Exception] should be thrownBy {
+                hospital.addDoctor(doctor)
+            }
+        }
+    }
+}