diff --git a/README.md b/README.md
index 1fddc9f..6c65e26 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@ Notes on studying Scala 3, mainly from reading documentation such as:
Aside from this README, the [tests](src/test/scala/Suite.scala) contain many runnable examples of some concepts explored here and many only explored there.
+There is also a [Jupyter notebooks](notebooks) directory containing a few more in-depth explorations of some of the content treated here.
+
## Syntax
### Assignment
diff --git a/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
new file mode 100644
index 0000000..d9de0ff
--- /dev/null
+++ b/notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
@@ -0,0 +1,375 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72",
+ "metadata": {},
+ "source": [
+ "# Notes on the \"Service-oriented design\" OOP domain modelling example\n",
+ "\n",
+ "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n",
+ "\n",
+ "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n",
+ "\n",
+ "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5",
+ "metadata": {},
+ "source": [
+ "## The `SubjectObserver` trait"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " type O <: Observer\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " private var observers: List[O] = List()\n",
+ " def subscribe(obs: O): Unit =\n",
+ " observers = obs :: observers\n",
+ " def publish() =\n",
+ " for obs <- observers do obs.notify(this)\n",
+ "\n",
+ " trait Observer:\n",
+ " def notify(sub: S): Unit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7",
+ "metadata": {},
+ "source": [
+ "### Top-level definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4a412a5-4252-4e57-9035-90b33581141d",
+ "metadata": {},
+ "source": [
+ "In the `SubjectObserver` trait's top level, four members are defined:\n",
+ "\n",
+ "Two abstract types:\n",
+ "- `S` (implying subject)\n",
+ "- `O` (implying observer)\n",
+ "\n",
+ "Two other traits:\n",
+ "- `Subject`\n",
+ "- `Observer`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa",
+ "metadata": {},
+ "source": [
+ "### Type definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59db0e6d-1013-4751-a81e-2cd204af5b22",
+ "metadata": {},
+ "source": [
+ "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n",
+ "\n",
+ "Meaning that:\n",
+ "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n",
+ "\n",
+ "Similarly, a type `O` is also defined, which must be a subtype of `Observer`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a03d3dbd-e675-45af-bda2-297b634bed19",
+ "metadata": {},
+ "source": [
+ "### Trait definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c",
+ "metadata": {},
+ "source": [
+ "Each minor trait has the following members:\n",
+ "\n",
+ "- **Subject**\n",
+ " - `private var observers: List[0] = List()`\n",
+ " - `def subscribe(obs: O: Unit = ...`\n",
+ " - `def publish() = ...`\n",
+ "- **Observer**\n",
+ " - `def notify(sub: S): Unit`\n",
+ "\n",
+ "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51",
+ "metadata": {},
+ "source": [
+ "#### Subject"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "786b27b8-e217-430f-bfed-e0ad227b2109",
+ "metadata": {},
+ "source": [
+ "The `Subject` trait defines three concrete members:\n",
+ "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n",
+ "- Two public, concrete methods whose behavior depend on this field:\n",
+ " - `subscribe(obs: O): Unit`\n",
+ " - `publish(): Unit`\n",
+ "\n",
+ "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n",
+ "\n",
+ "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a28c62aa-c39a-430d-989c-91941d331b81",
+ "metadata": {},
+ "source": [
+ "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n",
+ "\n",
+ "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c",
+ "metadata": {},
+ "source": [
+ "#### Observer"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3e028d1-1913-472d-96b8-44560239dd51",
+ "metadata": {},
+ "source": [
+ "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n",
+ "\n",
+ "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d",
+ "metadata": {},
+ "source": [
+ "## A `SubjectObserver` implementation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "object SensorReader extends SubjectObserver:\n",
+ " type S = Sensor\n",
+ " type O = Display\n",
+ "\n",
+ " class Sensor(val label: String) extends Subject:\n",
+ " private var currentValue = 0.0\n",
+ " def value = currentValue\n",
+ " def changeValue(v: Double) =\n",
+ " currentValue = v\n",
+ " publish()\n",
+ "\n",
+ " class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " println(s\"${sub.label} has value ${sub.value}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "96c9767d-9dcb-4a7d-9009-cc32cb319c2e",
+ "metadata": {},
+ "source": [
+ "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n",
+ "\n",
+ "To satisfy its contract, it defines:\n",
+ "- A type `S`, referring to the trait's \"subject\" concept, which is assigned the class `Sensor`, this class defined in the body of the object\n",
+ "- A type `O`, referring to the trait's \"observer\" concept, which is assigned to the class `Display`, also defined in the body of the object\n",
+ "\n",
+ "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n",
+ "\n",
+ "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n",
+ "- The class `Display` takes no arguments for its constructor and extends `Observer`\n",
+ "\n",
+ "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n",
+ "\n",
+ "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0. This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers.\n",
+ "\n",
+ "The `Display` class however must implement a `notify(sub: S)` method that handles a notification event.\n",
+ "\n",
+ "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`\n",
+ "\n",
+ "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef",
+ "metadata": {},
+ "source": [
+ "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n",
+ "\n",
+ "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n",
+ "\n",
+ "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n",
+ "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n",
+ "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab",
+ "metadata": {},
+ "source": [
+ "It also makes an interesting observation:\n",
+ "\n",
+ "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n",
+ "\n",
+ "It is alluding to the contract established in the nested `Observer` trait:\n",
+ "\n",
+ "```scala\n",
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " // ...\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " // ...\n",
+ "\n",
+ "trait Observer:\n",
+ " def notify(sub: S): Unit\n",
+ "```\n",
+ "\n",
+ "Because `S` "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c8afa140-88a1-450c-885a-af355f3ee3dc",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "id": "990c0e8a-6495-4561-8833-8e426d7af7e7",
+ "metadata": {},
+ "source": [
+ "## Invoking the `SensorReader` logic"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "sensor1 has value 2.0\n",
+ "sensor1 has value 2.0\n",
+ "sensor2 has value 3.0\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "\u001b[39m\n",
+ "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@dcc17ef\n",
+ "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@1b7edae8\n",
+ "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@7ea3d970\n",
+ "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@4c7c8b68"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import SensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "val s1 = Sensor(\"sensor1\")\n",
+ "val s2 = Sensor(\"sensor2\")\n",
+ "val d1 = SensorReader.Display()\n",
+ "val d2 = SensorReader.Display()\n",
+ "s1.subscribe(d1)\n",
+ "s1.subscribe(d2)\n",
+ "s2.subscribe(d1)\n",
+ "\n",
+ "// propagating updates through the network\n",
+ "s1.changeValue(2)\n",
+ "s2.changeValue(3)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Scala",
+ "language": "scala",
+ "name": "scala"
+ },
+ "language_info": {
+ "codemirror_mode": "text/x-scala",
+ "file_extension": ".sc",
+ "mimetype": "text/x-scala",
+ "name": "scala",
+ "nbconvert_exporter": "script",
+ "version": "2.13.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/README.md b/notebooks/README.md
new file mode 100644
index 0000000..5e69aa1
--- /dev/null
+++ b/notebooks/README.md
@@ -0,0 +1,9 @@
+
+# scala-notes notebooks
+
+These Jupyter Notebooks go a bit more in depth on some of the explored content:
+
+- [Service-oriented design](service-oriented-design)
+ - [Markdown](service-oriented-design/service-oriented-design.md)
+ - [HTML](service-oriented-design/service-oriented-design.html)
+ - [PDF](service-oriented-design/service-oriented-design.pdf)
diff --git a/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb b/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
new file mode 100644
index 0000000..79e53ff
--- /dev/null
+++ b/notebooks/service-oriented-design/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
@@ -0,0 +1,442 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72",
+ "metadata": {},
+ "source": [
+ "# Notes on the \"Service-oriented design\" OOP domain modelling example\n",
+ "\n",
+ "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n",
+ "\n",
+ "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n",
+ "\n",
+ "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5",
+ "metadata": {},
+ "source": [
+ "## The `SubjectObserver` trait"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " type O <: Observer\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " private var observers: List[O] = List()\n",
+ " def subscribe(obs: O): Unit =\n",
+ " observers = obs :: observers\n",
+ " def publish() =\n",
+ " for obs <- observers do obs.notify(this)\n",
+ "\n",
+ " trait Observer:\n",
+ " def notify(sub: S): Unit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7",
+ "metadata": {},
+ "source": [
+ "### Top-level definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4a412a5-4252-4e57-9035-90b33581141d",
+ "metadata": {},
+ "source": [
+ "In the `SubjectObserver` trait's top level, four members are defined:\n",
+ "\n",
+ "Two abstract types:\n",
+ "- `S` (implying subject)\n",
+ "- `O` (implying observer)\n",
+ "\n",
+ "Two other traits:\n",
+ "- `Subject`\n",
+ "- `Observer`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa",
+ "metadata": {},
+ "source": [
+ "### Type definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59db0e6d-1013-4751-a81e-2cd204af5b22",
+ "metadata": {},
+ "source": [
+ "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n",
+ "\n",
+ "Meaning that:\n",
+ "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n",
+ "\n",
+ "Similarly, a type `O` is also defined, which must be a subtype of `Observer`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a03d3dbd-e675-45af-bda2-297b634bed19",
+ "metadata": {},
+ "source": [
+ "### Trait definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c",
+ "metadata": {},
+ "source": [
+ "Each minor trait has the following members:\n",
+ "\n",
+ "```scala\n",
+ "trait Subject:\n",
+ " private var observers: List[O] = List()\n",
+ " def subscribe(obs: O): Unit = // ...\n",
+ " def publish() = // ...\n",
+ "trait Observer:\n",
+ " def notify(sub: S): Unit\n",
+ "```\n",
+ "\n",
+ "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51",
+ "metadata": {},
+ "source": [
+ "#### Subject"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "786b27b8-e217-430f-bfed-e0ad227b2109",
+ "metadata": {},
+ "source": [
+ "The `Subject` trait defines three concrete members:\n",
+ "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n",
+ "- Two public, concrete methods whose behavior depend on this field:\n",
+ " - `subscribe(obs: O): Unit`\n",
+ " - `publish(): Unit`\n",
+ "\n",
+ "These have concrete bodies defined as such:\n",
+ "\n",
+ "```scala\n",
+ "def subscribe(obs: O): Unit = observers = obs :: observers\n",
+ "def publish() = for obs <- observers do obs.notify(this)\n",
+ "```\n",
+ "\n",
+ "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n",
+ "\n",
+ "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a28c62aa-c39a-430d-989c-91941d331b81",
+ "metadata": {},
+ "source": [
+ "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n",
+ "\n",
+ "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c",
+ "metadata": {},
+ "source": [
+ "#### Observer"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3e028d1-1913-472d-96b8-44560239dd51",
+ "metadata": {},
+ "source": [
+ "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n",
+ "\n",
+ "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d",
+ "metadata": {},
+ "source": [
+ "## A `SubjectObserver` implementation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "object SensorReader extends SubjectObserver:\n",
+ " type S = Sensor\n",
+ " type O = Display\n",
+ "\n",
+ " class Sensor(val label: String) extends Subject:\n",
+ " private var currentValue = 0.0\n",
+ " def value = currentValue\n",
+ " def changeValue(v: Double) =\n",
+ " currentValue = v\n",
+ " publish()\n",
+ "\n",
+ " class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " println(s\"${sub.label} has value ${sub.value}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3baad2b4-c896-4a58-ba0a-9f45569eb7b1",
+ "metadata": {},
+ "source": [
+ "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n",
+ "\n",
+ "To satisfy its contract, it defines:\n",
+ "\n",
+ "```scala\n",
+ "type S = Sensor\n",
+ "type O = Display\n",
+ "```\n",
+ "\n",
+ "- A type `S` — referring to the trait's \"subject\" concept — assigned the class `Sensor`, this class defined in the body of the object\n",
+ "- A type `O` — referring to the trait's \"observer\" concept — which is assigned to the class `Display`, also defined in the body of the object"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ab7f33b6-1882-4f7a-b581-18e6f0069158",
+ "metadata": {},
+ "source": [
+ "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n",
+ "\n",
+ "```scala\n",
+ "class Sensor(val label: String) extends Subject:\n",
+ " // ....\n",
+ " \n",
+ "class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " // ...\n",
+ "```\n",
+ "\n",
+ "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n",
+ "- The class `Display` takes no arguments for its constructor and extends `Observer`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55d77fd6-085a-48f9-8873-f5b0e80135fa",
+ "metadata": {},
+ "source": [
+ "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n",
+ "\n",
+ "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0:\n",
+ "\n",
+ "```scala\n",
+ "class Sensor(val label: String) extends Subject:\n",
+ " private var currentValue = 0.0\n",
+ " def value = currentValue\n",
+ " def changeValue(v: Double) =\n",
+ " currentValue = v\n",
+ " publish()\n",
+ "```\n",
+ "\n",
+ "This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d0766a74-badf-4112-9260-c34b4d91b31c",
+ "metadata": {},
+ "source": [
+ "The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event.\n",
+ "\n",
+ "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`:\n",
+ "\n",
+ "```scala\n",
+ "class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " println(s\"${sub.label} has value ${sub.value}\")\n",
+ "```\n",
+ "\n",
+ "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef",
+ "metadata": {},
+ "source": [
+ "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n",
+ "\n",
+ "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n",
+ "\n",
+ "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n",
+ "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n",
+ "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab",
+ "metadata": {},
+ "source": [
+ "It also makes the following observation:\n",
+ "\n",
+ "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n",
+ "\n",
+ "It is alluding to the contract established in the nested `Observer` trait:\n",
+ "\n",
+ "```scala\n",
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " // ...\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " // ...\n",
+ "\n",
+ "trait Observer:\n",
+ " def notify(sub: S): Unit\n",
+ "```\n",
+ "\n",
+ "Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then:\n",
+ "\n",
+ "```scala\n",
+ "Sensor <: Subject && Sensor <: S\n",
+ "```\n",
+ "\n",
+ "Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called.\n",
+ "\n",
+ "The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "990c0e8a-6495-4561-8833-8e426d7af7e7",
+ "metadata": {},
+ "source": [
+ "## Invoking the `SensorReader` logic"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1a527837-272e-424f-9594-31d43bb1d4d9",
+ "metadata": {},
+ "source": [
+ "In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "sensor1 has value 2.0\n",
+ "sensor1 has value 2.0\n",
+ "sensor2 has value 3.0\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "\u001b[39m\n",
+ "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f\n",
+ "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9\n",
+ "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace\n",
+ "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import SensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "val s1 = Sensor(\"sensor1\")\n",
+ "val s2 = Sensor(\"sensor2\")\n",
+ "val d1 = SensorReader.Display()\n",
+ "val d2 = SensorReader.Display()\n",
+ "s1.subscribe(d1)\n",
+ "s1.subscribe(d2)\n",
+ "s2.subscribe(d1)\n",
+ "\n",
+ "// propagating updates through the network\n",
+ "s1.changeValue(2)\n",
+ "s2.changeValue(3)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Scala",
+ "language": "scala",
+ "name": "scala"
+ },
+ "language_info": {
+ "codemirror_mode": "text/x-scala",
+ "file_extension": ".sc",
+ "mimetype": "text/x-scala",
+ "name": "scala",
+ "nbconvert_exporter": "script",
+ "version": "2.13.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/service-oriented-design/service-oriented-design.html b/notebooks/service-oriented-design/service-oriented-design.html
new file mode 100644
index 0000000..1d481ee
--- /dev/null
+++ b/notebooks/service-oriented-design/service-oriented-design.html
@@ -0,0 +1,7965 @@
+
+
+
+
+
+service-oriented-design
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Out[1]:
+
+
defined trait SubjectObserver
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Out[2]:
+
+
defined object SensorReader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
sensor1 has value 2.0
+sensor1 has value 2.0
+sensor2 has value 3.0
+
+
+
+
+
Out[5]:
+
+
import SensorReader.*
+
+// setting up a network
+
+s1: Sensor = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f
+s2: Sensor = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9
+d1: Display = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace
+d2: Display = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a
+
+
+
+
+
+
+
+
diff --git a/notebooks/service-oriented-design/service-oriented-design.ipynb b/notebooks/service-oriented-design/service-oriented-design.ipynb
new file mode 100644
index 0000000..79e53ff
--- /dev/null
+++ b/notebooks/service-oriented-design/service-oriented-design.ipynb
@@ -0,0 +1,442 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72",
+ "metadata": {},
+ "source": [
+ "# Notes on the \"Service-oriented design\" OOP domain modelling example\n",
+ "\n",
+ "This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n",
+ "\n",
+ "The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n",
+ "\n",
+ "> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5",
+ "metadata": {},
+ "source": [
+ "## The `SubjectObserver` trait"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " type O <: Observer\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " private var observers: List[O] = List()\n",
+ " def subscribe(obs: O): Unit =\n",
+ " observers = obs :: observers\n",
+ " def publish() =\n",
+ " for obs <- observers do obs.notify(this)\n",
+ "\n",
+ " trait Observer:\n",
+ " def notify(sub: S): Unit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7",
+ "metadata": {},
+ "source": [
+ "### Top-level definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4a412a5-4252-4e57-9035-90b33581141d",
+ "metadata": {},
+ "source": [
+ "In the `SubjectObserver` trait's top level, four members are defined:\n",
+ "\n",
+ "Two abstract types:\n",
+ "- `S` (implying subject)\n",
+ "- `O` (implying observer)\n",
+ "\n",
+ "Two other traits:\n",
+ "- `Subject`\n",
+ "- `Observer`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e1e377f-941d-436b-a8d7-9e28683b3efa",
+ "metadata": {},
+ "source": [
+ "### Type definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59db0e6d-1013-4751-a81e-2cd204af5b22",
+ "metadata": {},
+ "source": [
+ "The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n",
+ "\n",
+ "Meaning that:\n",
+ "> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n",
+ "\n",
+ "Similarly, a type `O` is also defined, which must be a subtype of `Observer`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a03d3dbd-e675-45af-bda2-297b634bed19",
+ "metadata": {},
+ "source": [
+ "### Trait definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c",
+ "metadata": {},
+ "source": [
+ "Each minor trait has the following members:\n",
+ "\n",
+ "```scala\n",
+ "trait Subject:\n",
+ " private var observers: List[O] = List()\n",
+ " def subscribe(obs: O): Unit = // ...\n",
+ " def publish() = // ...\n",
+ "trait Observer:\n",
+ " def notify(sub: S): Unit\n",
+ "```\n",
+ "\n",
+ "All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51",
+ "metadata": {},
+ "source": [
+ "#### Subject"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "786b27b8-e217-430f-bfed-e0ad227b2109",
+ "metadata": {},
+ "source": [
+ "The `Subject` trait defines three concrete members:\n",
+ "- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n",
+ "- Two public, concrete methods whose behavior depend on this field:\n",
+ " - `subscribe(obs: O): Unit`\n",
+ " - `publish(): Unit`\n",
+ "\n",
+ "These have concrete bodies defined as such:\n",
+ "\n",
+ "```scala\n",
+ "def subscribe(obs: O): Unit = observers = obs :: observers\n",
+ "def publish() = for obs <- observers do obs.notify(this)\n",
+ "```\n",
+ "\n",
+ "The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n",
+ "\n",
+ "The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a28c62aa-c39a-430d-989c-91941d331b81",
+ "metadata": {},
+ "source": [
+ "Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n",
+ "\n",
+ "> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c",
+ "metadata": {},
+ "source": [
+ "#### Observer"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3e028d1-1913-472d-96b8-44560239dd51",
+ "metadata": {},
+ "source": [
+ "The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n",
+ "\n",
+ "My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d",
+ "metadata": {},
+ "source": [
+ "## A `SubjectObserver` implementation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "object SensorReader extends SubjectObserver:\n",
+ " type S = Sensor\n",
+ " type O = Display\n",
+ "\n",
+ " class Sensor(val label: String) extends Subject:\n",
+ " private var currentValue = 0.0\n",
+ " def value = currentValue\n",
+ " def changeValue(v: Double) =\n",
+ " currentValue = v\n",
+ " publish()\n",
+ "\n",
+ " class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " println(s\"${sub.label} has value ${sub.value}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3baad2b4-c896-4a58-ba0a-9f45569eb7b1",
+ "metadata": {},
+ "source": [
+ "Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n",
+ "\n",
+ "To satisfy its contract, it defines:\n",
+ "\n",
+ "```scala\n",
+ "type S = Sensor\n",
+ "type O = Display\n",
+ "```\n",
+ "\n",
+ "- A type `S` — referring to the trait's \"subject\" concept — assigned the class `Sensor`, this class defined in the body of the object\n",
+ "- A type `O` — referring to the trait's \"observer\" concept — which is assigned to the class `Display`, also defined in the body of the object"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ab7f33b6-1882-4f7a-b581-18e6f0069158",
+ "metadata": {},
+ "source": [
+ "It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n",
+ "\n",
+ "```scala\n",
+ "class Sensor(val label: String) extends Subject:\n",
+ " // ....\n",
+ " \n",
+ "class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " // ...\n",
+ "```\n",
+ "\n",
+ "- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n",
+ "- The class `Display` takes no arguments for its constructor and extends `Observer`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55d77fd6-085a-48f9-8873-f5b0e80135fa",
+ "metadata": {},
+ "source": [
+ "The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n",
+ "\n",
+ "However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0:\n",
+ "\n",
+ "```scala\n",
+ "class Sensor(val label: String) extends Subject:\n",
+ " private var currentValue = 0.0\n",
+ " def value = currentValue\n",
+ " def changeValue(v: Double) =\n",
+ " currentValue = v\n",
+ " publish()\n",
+ "```\n",
+ "\n",
+ "This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d0766a74-badf-4112-9260-c34b4d91b31c",
+ "metadata": {},
+ "source": [
+ "The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event.\n",
+ "\n",
+ "It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`:\n",
+ "\n",
+ "```scala\n",
+ "class Display extends Observer:\n",
+ " def notify(sub: Sensor) =\n",
+ " println(s\"${sub.label} has value ${sub.value}\")\n",
+ "```\n",
+ "\n",
+ "The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "688f3a5b-510c-48a2-8b05-8273a3d220ef",
+ "metadata": {},
+ "source": [
+ "The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n",
+ "\n",
+ "> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n",
+ "\n",
+ "> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n",
+ "> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n",
+ "> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab",
+ "metadata": {},
+ "source": [
+ "It also makes the following observation:\n",
+ "\n",
+ "> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n",
+ "\n",
+ "It is alluding to the contract established in the nested `Observer` trait:\n",
+ "\n",
+ "```scala\n",
+ "trait SubjectObserver:\n",
+ " type S <: Subject\n",
+ " // ...\n",
+ "\n",
+ " trait Subject:\n",
+ " self: S =>\n",
+ " // ...\n",
+ "\n",
+ "trait Observer:\n",
+ " def notify(sub: S): Unit\n",
+ "```\n",
+ "\n",
+ "Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then:\n",
+ "\n",
+ "```scala\n",
+ "Sensor <: Subject && Sensor <: S\n",
+ "```\n",
+ "\n",
+ "Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called.\n",
+ "\n",
+ "The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "990c0e8a-6495-4561-8833-8e426d7af7e7",
+ "metadata": {},
+ "source": [
+ "## Invoking the `SensorReader` logic"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1a527837-272e-424f-9594-31d43bb1d4d9",
+ "metadata": {},
+ "source": [
+ "In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "sensor1 has value 2.0\n",
+ "sensor1 has value 2.0\n",
+ "sensor2 has value 3.0\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "\u001b[39m\n",
+ "\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f\n",
+ "\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9\n",
+ "\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace\n",
+ "\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import SensorReader.*\n",
+ "\n",
+ "// setting up a network\n",
+ "val s1 = Sensor(\"sensor1\")\n",
+ "val s2 = Sensor(\"sensor2\")\n",
+ "val d1 = SensorReader.Display()\n",
+ "val d2 = SensorReader.Display()\n",
+ "s1.subscribe(d1)\n",
+ "s1.subscribe(d2)\n",
+ "s2.subscribe(d1)\n",
+ "\n",
+ "// propagating updates through the network\n",
+ "s1.changeValue(2)\n",
+ "s2.changeValue(3)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Scala",
+ "language": "scala",
+ "name": "scala"
+ },
+ "language_info": {
+ "codemirror_mode": "text/x-scala",
+ "file_extension": ".sc",
+ "mimetype": "text/x-scala",
+ "name": "scala",
+ "nbconvert_exporter": "script",
+ "version": "2.13.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/service-oriented-design/service-oriented-design.md b/notebooks/service-oriented-design/service-oriented-design.md
new file mode 100644
index 0000000..cfa8527
--- /dev/null
+++ b/notebooks/service-oriented-design/service-oriented-design.md
@@ -0,0 +1,257 @@
+# Notes on the "Service-oriented design" OOP domain modelling example
+
+This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.
+
+The example is in turn based on the paper by Martin Odersky and Matthias Zenger, ["Scalable Component Abstractions"](https://doi.org/10.1145/1094811.1094815).
+
+> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)
+
+## The `SubjectObserver` trait
+
+
+```scala
+trait SubjectObserver:
+ type S <: Subject
+ type O <: Observer
+
+ trait Subject:
+ self: S =>
+ private var observers: List[O] = List()
+ def subscribe(obs: O): Unit =
+ observers = obs :: observers
+ def publish() =
+ for obs <- observers do obs.notify(this)
+
+ trait Observer:
+ def notify(sub: S): Unit
+```
+
+
+
+
+ defined [32mtrait[39m [36mSubjectObserver[39m
+
+
+
+### Top-level definitions
+
+In the `SubjectObserver` trait's top level, four members are defined:
+
+Two abstract types:
+- `S` (implying subject)
+- `O` (implying observer)
+
+Two other traits:
+- `Subject`
+- `Observer`
+
+### Type definitions
+
+The syntax `type S <: Subject`, called an "**upper bound**", is a declaration for an abstract type `S` which must be a subtype of `Subject`.
+
+Meaning that:
+> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.
+
+Similarly, a type `O` is also defined, which must be a subtype of `Observer`.
+
+### Trait definitions
+
+Each minor trait has the following members:
+
+```scala
+trait Subject:
+ private var observers: List[O] = List()
+ def subscribe(obs: O): Unit = // ...
+ def publish() = // ...
+trait Observer:
+ def notify(sub: S): Unit
+```
+
+All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract.
+
+#### Subject
+
+The `Subject` trait defines three concrete members:
+- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`
+- Two public, concrete methods whose behavior depend on this field:
+ - `subscribe(obs: O): Unit`
+ - `publish(): Unit`
+
+These have concrete bodies defined as such:
+
+```scala
+def subscribe(obs: O): Unit = observers = obs :: observers
+def publish() = for obs <- observers do obs.notify(this)
+```
+
+The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.
+
+The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument.
+
+Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:
+
+> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`.
+
+#### Observer
+
+The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.
+
+My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something.
+
+## A `SubjectObserver` implementation
+
+
+```scala
+object SensorReader extends SubjectObserver:
+ type S = Sensor
+ type O = Display
+
+ class Sensor(val label: String) extends Subject:
+ private var currentValue = 0.0
+ def value = currentValue
+ def changeValue(v: Double) =
+ currentValue = v
+ publish()
+
+ class Display extends Observer:
+ def notify(sub: Sensor) =
+ println(s"${sub.label} has value ${sub.value}")
+```
+
+
+
+
+ defined [32mobject[39m [36mSensorReader[39m
+
+
+
+Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.
+
+To satisfy its contract, it defines:
+
+```scala
+type S = Sensor
+type O = Display
+```
+
+- A type `S` — referring to the trait's "subject" concept — assigned the class `Sensor`, this class defined in the body of the object
+- A type `O` — referring to the trait's "observer" concept — which is assigned to the class `Display`, also defined in the body of the object
+
+It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:
+
+```scala
+class Sensor(val label: String) extends Subject:
+ // ....
+
+class Display extends Observer:
+ def notify(sub: Sensor) =
+ // ...
+```
+
+- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`
+- The class `Display` takes no arguments for its constructor and extends `Observer`
+
+The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.
+
+However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0:
+
+```scala
+class Sensor(val label: String) extends Subject:
+ private var currentValue = 0.0
+ def value = currentValue
+ def changeValue(v: Double) =
+ currentValue = v
+ publish()
+```
+
+This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers.
+
+The `Display` class must yet implement a `notify(sub: S)` method that handles a notification event.
+
+It does so by printing to STDOUT the interpolated string `"${sub.label} has value ${sub.value}"`:
+
+```scala
+class Display extends Observer:
+ def notify(sub: Sensor) =
+ println(s"${sub.label} has value ${sub.value}")
+```
+
+The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait.
+
+The documentation emphasizes how this design demonstrates an object-oriented paradigm:
+
+> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:
+
+> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.
+> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.
+> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`.
+
+It also makes the following observation:
+
+> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.
+
+It is alluding to the contract established in the nested `Observer` trait:
+
+```scala
+trait SubjectObserver:
+ type S <: Subject
+ // ...
+
+ trait Subject:
+ self: S =>
+ // ...
+
+trait Observer:
+ def notify(sub: S): Unit
+```
+
+Considering that `S` had to be a subtype of `Subject`, as per its `type S <: Subject` definition, and given the self-type annotation `self: S =>` in the body of the `Subject` trait established that the subtypes of `Subject` (in this case, `Sensor`) have to also be subtypes of `S`, then:
+
+```scala
+Sensor <: Subject && Sensor <: S
+```
+
+Because of this double constraint, `Sensor` can pass itself as a subtype of `Subject` instead of as an instance of `Subject` when `publish(this)` is called.
+
+The implication of safety being, possibly, that [through subtyping](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#subtyping), the instance of `Sensor` can be used where an extended implementation of `Subject` is expected, but insofar as it is also the type assigned to `S`.
+
+## Invoking the `SensorReader` logic
+
+In the example below, the output is two times an identical message printed for the same change in the `currentValue` of `sensor1`, since it has two different observers subscribed (`d1` and `d2`), and a single message printed for the change to the value of `s2` from its one observer `d1`.
+
+
+```scala
+import SensorReader.*
+
+// setting up a network
+val s1 = Sensor("sensor1")
+val s2 = Sensor("sensor2")
+val d1 = SensorReader.Display()
+val d2 = SensorReader.Display()
+s1.subscribe(d1)
+s1.subscribe(d2)
+s2.subscribe(d1)
+
+// propagating updates through the network
+s1.changeValue(2)
+s2.changeValue(3)
+```
+
+ sensor1 has value 2.0
+ sensor1 has value 2.0
+ sensor2 has value 3.0
+
+
+
+
+
+ [32mimport [39m[36mSensorReader.*
+
+ // setting up a network
+ [39m
+ [36ms1[39m: [32mSensor[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@2a8e138f
+ [36ms2[39m: [32mSensor[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@10fc52e9
+ [36md1[39m: [32mDisplay[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@38234ace
+ [36md2[39m: [32mDisplay[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@182f632a
+
+
diff --git a/notebooks/service-oriented-design/service-oriented-design.pdf b/notebooks/service-oriented-design/service-oriented-design.pdf
new file mode 100644
index 0000000..f0f5531
Binary files /dev/null and b/notebooks/service-oriented-design/service-oriented-design.pdf differ