diff --git a/.gitignore b/.gitignore
index b6ba45782c613cd095dd01104b99918dacfc7224..ad8887e2b254d1243679dcf1522a2f4039ae1632 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@
 .ipynb_checkpoints
 *.gif
 Manifest.toml
+# For MacOS
+.DS_Store
diff --git a/2_Advanced_Data_Types_de.ipynb b/2_Advanced_Data_Types_de.ipynb
index 36325fcd5eb2c8a6f0f1ce48725cdaf7fefb8dad..77ace2db19c36675df4bef01882b2f1c5e43ed1e 100644
--- a/2_Advanced_Data_Types_de.ipynb
+++ b/2_Advanced_Data_Types_de.ipynb
@@ -1125,7 +1125,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Julia 1.9.3",
+   "display_name": "julia 1.9.3",
    "language": "julia",
    "name": "julia-1.9"
   },
diff --git a/2_Advanced_Data_Types_en.ipynb b/2_Advanced_Data_Types_en.ipynb
index 5161c0bc3096d70a32d7e34f9e15dea5e2247708..c7f953b6c30391df7884183c27208639b0268c08 100644
--- a/2_Advanced_Data_Types_en.ipynb
+++ b/2_Advanced_Data_Types_en.ipynb
@@ -1126,7 +1126,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Julia 1.9.3",
+   "display_name": "julia 1.9.3",
    "language": "julia",
    "name": "julia-1.9"
   },
diff --git a/3_Data_Types_Multiple_Dispatch_de.ipynb b/3_Data_Types_Multiple_Dispatch_de.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..274e089028825682d0a7d02c13abd5bd87d84357
--- /dev/null
+++ b/3_Data_Types_Multiple_Dispatch_de.ipynb
@@ -0,0 +1,1188 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "f59de749-6cd1-4f77-b89e-57b110d83858",
+   "metadata": {},
+   "source": [
+    "## Konstruktoren\n",
+    "\n",
+    "Bevor wir in diesem Notebook weitermachen, benötigen wir noch eine letzte Zutat aus Strukturen: benutzerdefinierte Konstruktoren.\n",
+    "Ein Konstruktor ist eine spezielle Funktion, die aufgerufen wird, um ein Objekt zu erstellen. Wir nehmen als Beispiel eine `Datum` Struktur."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c171ec1a-902b-4211-b250-373953f4c58c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Date\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c39605b9-2667-4c9d-8b1f-f6505baa0838",
+   "metadata": {},
+   "source": [
+    "Wir können ein neues Datum instanziieren, indem wir den Standardkonstruktor aufrufen."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "63630a93-6d93-489a-9656-9ac6b5be86f5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "date = Date(1, 11, 2023)\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2cad81b4-b9df-42f3-a89b-8340036bbe8e",
+   "metadata": {},
+   "source": [
+    "Wenn wir jedoch mit dem Standardverhalten des Konstruktors nicht zufrieden sind, können wir unseren eigenen Konstruktor definieren, der ein benutzerdefiniertes Verhalten implementiert. Zum Beispiel könnten wir an einem Konstruktor interessiert sein, der eine Zeichenkette im Format `TT.MM.JJJJ` als Eingabe akzeptiert.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "34e84e19-734a-42c0-b263-2dc25128c0cd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function Date(str::String)\n",
+    "    parts = split(str, \".\")\n",
+    "    if length(parts) != 3\n",
+    "        throw(ArgumentError(\"Invalid date format. Use DD.MM.YYYY\"))\n",
+    "    end\n",
+    "\n",
+    "    d = parse(Int, parts[1])\n",
+    "    m = parse(Int, parts[2])\n",
+    "    y = parse(Int, parts[3])\n",
+    "\n",
+    "    return Date(d, m, y)\n",
+    "end\n",
+    "\n",
+    "date = Date(\"01.11.2023\")\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "915b71b1-c188-4c82-a2ff-810a58f39720",
+   "metadata": {},
+   "source": [
+    "Alternativ können Konstruktoren auch innerhalb der Struktur definiert werden. Der Vorteil dieser Strategie ist, dass wir den Standardkonstruktor durch benutzerdefiniertes Verhalten ersetzen."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6dda7612-0cd2-471a-93e4-119a4e2f8f93",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct ValidatedDate\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "    function ValidatedDate(day, month, year)\n",
+    "        @assert(day > 0, \"day is negative\")\n",
+    "        @assert(month > 0, \"month is negative\")\n",
+    "        @assert(year > 0, \"year is negative\")\n",
+    "        new(day, month, year)\n",
+    "    end\n",
+    "end\n",
+    "\n",
+    "date = ValidatedDate(-1, 10, 2023)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "89ab4f63-bd67-4cca-919a-f0d8f2bc88d1",
+   "metadata": {},
+   "source": [
+    "### Übungen\n",
+    "\n",
+    "  - Schreibe einen benutzerdefinierten Konstruktor, der nur den Monat und das Jahr als Eingabeargumente akzeptiert und den Tag auf 1 setzt."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "884e42ed-d340-410c-ad42-950e173807c8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "459a7d50-1138-4fc1-af46-a0bdb4335287",
+   "metadata": {},
+   "source": [
+    "  - Passe den `ValidatedDate` inneren Konstruktor so an, dass er überprüft, ob der angegebene Tag im angegebenen Monat existiert. Nehmen Sie an, dass der Februar immer 28 Tage hat."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eeb10ef4-0bb5-46e0-96b4-0fb98e4e1b3d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "169512ca-868f-406d-b8c8-8e9174d25b64",
+   "metadata": {},
+   "source": [
+    "  - Optional: Passe das vorherige Ergebnis an und berücksichtige auch Schaltjahre.\n",
+    "\n",
+    "    `Jedes Jahr, das genau durch vier teilbar ist, ist ein Schaltjahr, mit Ausnahme von Jahren, die genau durch 100 teilbar sind. Diese Jahrhundertjahre sind jedoch Schaltjahre, wenn sie genau durch 400 teilbar sind. Zum Beispiel sind die Jahre 1700, 1800 und 1900 keine Schaltjahre, aber die Jahre 1600 und 2000 sind es.`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8b1dad51-d7cf-43aa-a2c8-9b3eaa4a1f4d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3ba487cb",
+   "metadata": {},
+   "source": [
+    "# Datentypen und Multiple Dispatch\n",
+    "\n",
+    "In diesem Arbeitsblatt werden wir die grundlegenden Konzepte von Julias Typsystem erforschen und Multiple Dispatch verstehen, wobei wir uns konzentrieren auf:\n",
+    "\n",
+    "- Abstrakte und Konkrete Typen\n",
+    "- Dynamische Typen\n",
+    "- Multiple Dispatch\n",
+    "\n",
+    "Diese Konzepte verbessern Julias Leistung, insbesondere bei mathematischer und wissenschaftlicher Datenverarbeitung."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fe56dbef",
+   "metadata": {},
+   "source": [
+    "## Abstrakte and konkrete Typen"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "01604e4c",
+   "metadata": {},
+   "source": [
+    "Bevor wir den Multiple Dispatch von Funktionen und den Dispatch nach Typen besprechen, werfen wir kurz einen Blick auf Julias Typsystem. \n",
+    "Typen in Julia fallen in zwei Kategorien: **Abstrakt** und **konkret**.\n",
+    "\n",
+    "- **Abstrakte Typen**: Können nicht instanziiert werden und dienen dazu, allgemeine Kategorien von Objekten darzustellen.\n",
+    "- **Konkrete Typen**: Können instanziiert werden und werden verwendet, um Objekte zu erstellen.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "41e57dbd",
+   "metadata": {},
+   "source": [
+    "In Julia gibt es mehrere eingebaute Funktionen, die sich auf das Abfragen und Arbeiten mit Typen beziehen.\n",
+    "Hier eine Auswahl einiger wichtiger:\n",
+    "\n",
+    "- `<:`: Der Subtyp-Operator, der verwendet wird, um zu überprüfen, ob ein Typ ein Subtyp eines anderen Typs ist.\n",
+    "- `isabstracttype(T)`: Überprüfen, ob T ein abstrakter Typ ist.\n",
+    "- `isconcretetype(T)`: Überprüfen, ob T ein konkreter Typ ist.\n",
+    "- `subtypes(T)`: Eine Liste aller unmittelbaren Subtypen des Typs T erhalten.\n",
+    "- `supertype(T)`: Den direkten Supertyp des Typs T erhalten.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "da96b9b9",
+   "metadata": {},
+   "source": [
+    "Abstrakte Typen wie `Integer` oder `Number` sind Supertypen von vielen anderen Typen, zum Beispiel:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "50b00765",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show Int32 <: Integer   # Read:Int32 is a sub-type of Integer\n",
+    "@show UInt16 <: Integer  # UInt16 is a sub-type of Integer\n",
+    "@show Float32 <: Integer # Float32 is not a sub-type of Integer\n",
+    "@show Float32 <: Number  # Float32 is a sub-type of Number\n",
+    "@show Integer <: Number; # Integer is a sub-type of Nummber\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "329380e7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# by transitivity:\n",
+    "@show Int32  <: Number\n",
+    "@show UInt16 <: Number\n",
+    "@show Number <: Number;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "027b76c8",
+   "metadata": {},
+   "source": [
+    "### Eigenschaften von Typen\n",
+    "Wir können Typ-Eigenschaften auf verschiedene Weisen überprüfen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "49228132",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isconcretetype(Int32);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ffd1775",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Integer);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2a316d56",
+   "metadata": {},
+   "source": [
+    "Eine ausgefallene Möglichkeit ist sogar, einen Typ-Baum anzuzeigen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f5a1fd53",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "    print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "\n",
+    "Print a tree structure that visualizes the type hierarchy rooted at `T`.\n",
+    "\n",
+    "# Parameters\n",
+    "- `T`: The type which serves as the root of the tree to print.\n",
+    "- `level`: (Optional, default=`0`) An integer specifying the current recursion depth - typically not provided by the user.\n",
+    "- `max_level`: (Optional, default=`typemax(Int)`) An integer specifying the maximum recursion depth, i.e., how many levels deep into the type hierarchy to print.\n",
+    "- `prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "- `subtype_prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "\n",
+    "# Usage\n",
+    "```julia\n",
+    "print_type_tree(Number, max_level=7)\n",
+    "```\n",
+    "\"\"\"\n",
+    "function print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "    # Stop recursion if max level is reached\n",
+    "    if level >= max_level\n",
+    "        return\n",
+    "    end\n",
+    "\n",
+    "    # Print current type\n",
+    "    println(prefix, subtype_prefix, T)\n",
+    "\n",
+    "    # Obtain subtypes\n",
+    "    subs = subtypes(T)\n",
+    "\n",
+    "    # Recursively print subtypes with adjusted prefix\n",
+    "    for (i, subtype) in enumerate(subs)\n",
+    "        new_prefix = \"    \"\n",
+    "        subtype_prefix = i < length(subs) ? \"├── \" : \"└── \"\n",
+    "\n",
+    "        # If the subtype has no further subtypes, avoid using the \"|\" in the prefix\n",
+    "        if isempty(subtypes(subtype))\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * \"    \", subtype_prefix)\n",
+    "        else\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix)\n",
+    "        end\n",
+    "    end\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "41f4de49",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Number)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bc4662dc",
+   "metadata": {},
+   "source": [
+    "**Übung**: Schreiben Sie Code, um alle Subtypen von `Number` zu bestimmen, unabhängig davon, ob sie abstrakt oder konkret sind.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39b8339f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3ad36e86",
+   "metadata": {},
+   "source": [
+    "Um den Supertyp eines Typs zu erhalten, kann man `supertype` verwenden:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e666712d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show supertype(Int64)\n",
+    "@show supertype(Signed)\n",
+    "@show supertype(Float16)\n",
+    "@show supertype(Float32)\n",
+    "@show supertype(Bool);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "147db9b0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function print_supertypes(T::Type)\n",
+    "    println(\"Supertypes of $T:\")\n",
+    "    while T != Any\n",
+    "        print(T, \" ---> \")\n",
+    "        T = supertype(T)\n",
+    "    end\n",
+    "    println(T)  # Print Any, which is the ultimate supertype of all types\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ed566f15",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Int64);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1462bc0c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Float64);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d16d0857",
+   "metadata": {},
+   "source": [
+    "### Benutzerdefinierte Abstrakte und Konkrete Typen\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "882efff3",
+   "metadata": {},
+   "source": [
+    "Man kann benutzerdefinierte abstrakte Typen mit `abstract type` definieren:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3ebf46f6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Animal end  # Abstract type\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d63c0261",
+   "metadata": {},
+   "source": [
+    "Dann können auch einige konkrete Typen erstellt werden:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eaab065e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Dog <: Animal  # Concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n",
+    "\n",
+    "struct Cat <: Animal  # Another concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "091a7905",
+   "metadata": {},
+   "source": [
+    "In diesem Beispiel erstellen wir zwei konkrete Tiere, `Dog` und `Cat`.\n",
+    "Man kann `subtypes` verwenden, um alle Subtypen eines abstrakten oder konkreten Typs zu erhalten.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b8b7fca8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "subtypes(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a2f3177d",
+   "metadata": {},
+   "source": [
+    "Wiederum können wir mit `isabstracttype` und `isconcretetype` überprüfen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3eced325",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Animal)\n",
+    "@show isabstracttype(Dog)\n",
+    "@show isabstracttype(Cat)\n",
+    "\n",
+    "@show isconcretetype(Animal)\n",
+    "@show isconcretetype(Dog)\n",
+    "@show isconcretetype(Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4b08f9f7",
+   "metadata": {},
+   "source": [
+    "Der Typ-Baum von `Animal` ist:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "40debabd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "87e2a153",
+   "metadata": {},
+   "source": [
+    "Jetzt erstellen wir zwei Instanzen von den konkreten Typen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6f0f092f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a_dog = Dog(\"Buddy\", 3)\n",
+    "a_cat = Cat(\"Kitty\", 2)\n",
+    "\n",
+    "@show a_dog a_cat;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "41ea3797",
+   "metadata": {},
+   "source": [
+    "In Julia wird die Methode `isa` verwendet, um zu bestimmen, ob eine Instanz von einem bestimmten Typ ist, egal ob er abstrakt oder konkret ist:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "263461b1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isa(a_dog, Dog)\n",
+    "@show isa(a_dog, Animal)\n",
+    "@show isa(a_dog, Cat)\n",
+    "@show isa(a_cat, Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee934bc9",
+   "metadata": {},
+   "source": [
+    "Die Methode `typeof` wird verwendet, um den Typ einer Instanz zu erhalten:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "208aed02",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show typeof(a_dog)\n",
+    "@show typeof(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8dc6aef6",
+   "metadata": {},
+   "source": [
+    "Wir können auch alle Supertypen von `Dog` und `Cat` erhalten:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b3ca83e6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Dog)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "662a732f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Cat)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d850444b",
+   "metadata": {},
+   "source": [
+    "### Übungen\n",
+    "Schreiben Sie Code, um einige abstrakte und konkrete Subtypen von `Animal` zu implementieren, und verwenden Sie `print_type_tree`, um den Typ-Baum anzusehen. Zum Beispiel können Sie den abstrakten Typ `Bird` implementieren und dann einige Arten von Vögeln.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2fbfbb88",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0add9f3d",
+   "metadata": {},
+   "source": [
+    "Welche der folgenden Typen sind Untertypen eines anderen? Versuchen Sie zuerst zu raten und überprüfen Sie dann mithilfe des Operators `<:`.\n",
+    "\n",
+    "```julia\n",
+    "Float64     AbstractFloat      Integer\n",
+    "Number      AbstractArray      Complex\n",
+    "Real        Any                Nothing\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "688afde5-1123-4adc-affd-687b966f387e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "77bb1fe2",
+   "metadata": {},
+   "source": [
+    "In Julia sind konkrete Typen immer ein Blatt des Typbaums, d.h., sie können nicht voneinander abgeleitet werden. Für jemanden, der mit C++ oder Python vertraut ist (wie einige von uns), mag dies zunächst einschränkend erscheinen, aber es entfernt eine Menge unnötiger Komplexität aus der Typenhierarchie. Wir werden jetzt keine weiteren Informationen geben, der Grund wird am Ende dieses Notebooks klarer sein.\n",
+    "\n",
+    "Weitere Details finden Sie unter: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types).\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "64fc3a0b",
+   "metadata": {},
+   "source": [
+    "## Mehrfachdispatch (*multiple  dispatch*)\n",
+    "\n",
+    "Mehrfachdispatch ist wahrscheinlich das Schlüsselmerkmal von Julia, das es im Vergleich zu vielen anderen Sprachen unterscheidet und ihm die Fähigkeit verleiht, gleichzeitig flexibel und leistungsstark zu sein.\n",
+    "\n",
+    "Um das Konzept des Mehrfachdispatchs vollständig zu verstehen, werden wir eine Analogie verwenden. Wir werden so tun, als ob eine Programmiersprache ein Werkzeug zum Schreiben eines Kochbuches ist. Ein Rezept kombiniert eine Zubereitungsmethode (zum Beispiel Backen, Braten) mit einer Zutat (zum Beispiel Kartoffeln, Karotten, Fisch).\n",
+    "\n",
+    "- Die erste Möglichkeit besteht darin, das Buch nach Zubereitungsmethoden zu organisieren: Jedes Kapitel erklärt ausführlich eine Zubereitungsmethode. Zum Beispiel haben wir **Kapitel 1: Backen**, **Kapitel 2: Braten** und so weiter. Der Nachteil dieses Ansatzes ist, dass wir immer dann, wenn wir eine neue Zutat hinzufügen, mehrere Kapitel ändern müssen. Dieser Ansatz, der sich auf die Aktion und nicht auf die Zutaten konzentriert, ist typisch für funktionale Programmiersprachen.\n",
+    "\n",
+    "- Die zweite Möglichkeit besteht darin, das Buch nach Zutaten zu organisieren: Jedes Kapitel konzentriert sich auf eine Zutat. Zum Beispiel haben wir **Kapitel 1: Kartoffeln**, **Kapitel 2: Fisch** und so weiter. Der Nachteil dieses Ansatzes ist, dass wir immer dann, wenn wir ein neues Rezept hinzufügen, erneut mehrere Kapitel ändern müssen. Dieser Ansatz konzentriert sich auf die Zutaten (Daten) und ist typisch für objektorientierte Programmierung, bei der wir etwas Ähnliches haben könnten wie:\n",
+    "    ```julia\n",
+    "    struct Kartoffeln\n",
+    "        function backen()\n",
+    "        function braten()\n",
+    "    end\n",
+    "    struct Fisch\n",
+    "        function backen()\n",
+    "        function braten()\n",
+    "    end\n",
+    "    ```\n",
+    "\n",
+    "- Julia verfolgt einen dritten Ansatz namens **Mehrfachdispatch**, bei dem die Aktion von den Daten entkoppelt wird. In unserem hypothetischen Kochbuch haben wir Kapitel wie **Kapitel 1: Kartoffeln backen**, **Kapitel 2: Kartoffeln braten**, **Kapitel 3: Fisch backen**, **Kapitel 4: Fisch braten** und so weiter. Jedes dieser Kapitel enthält etwas Ähnliches wie:\n",
+    "    ```julia\n",
+    "    function backen(kartoffeln::Kartoffeln)\n",
+    "    function braten(kartoffeln::Kartoffeln)\n",
+    "    function backen(fisch::Fisch)\n",
+    "    function braten(fisch::Fisch) \n",
+    "    ```\n",
+    "\n",
+    "Auf diese Weise erfordert das Hinzufügen eines neuen Rezepts für eine bestimmte Art von Lebensmittel keine Änderungen an bereits geschriebenen Dingen.\n",
+    "\n",
+    "Quelle: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0198d051",
+   "metadata": {},
+   "source": [
+    "Kehren wir zur `mymult`-Funktion zurück und sehen, wie wir den Mehrfachdispatch verwenden können, um neue Funktionen zu implementieren:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c43dc846",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(x, y) = x * y\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a088c5fd",
+   "metadata": {},
+   "source": [
+    "Wir konnten diese Funktionen sicher mit einer Vielzahl von Typenkombinationen verwenden, aber einige Dinge funktionieren noch nicht:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "65dbe501",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1eda2f65",
+   "metadata": {},
+   "source": [
+    "Angenommen, wir möchten den String `str` $n$-mal aneinanderreihen, indem wir ihn mit einer ganzen Zahl $n$ multiplizieren. In Julia ist diese Funktionalität bereits durch den Potenzoperator implementiert:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8c18999b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"abc\"^4\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "13b4953e",
+   "metadata": {},
+   "source": [
+    "Aber der Argumentation halber nehmen wir an, wir möchten, dass `mymult(\"abc\", 4)` und `mymult(4, \"abc\")` auf die gleiche Weise funktionieren. Wir definieren zwei spezielle Methoden:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fa0af502",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(str::String, n::Integer) = str^n\n",
+    "mymult(n::Integer, str::String) = mymult(str, n)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ed42c2c2",
+   "metadata": {},
+   "source": [
+    "In beiden Fällen bedeutet die Syntax `str::String` und `n::Integer`, dass die jeweilige Methode nur während der Zuordnung berücksichtigt wird, wenn das Argument `str` vom Typ `String` ist und `n` ein `Integer` (oder Untertyp) ist. Da Julia immer zur spezifischsten Methode dispatcht, falls mehrere Methoden übereinstimmen, ist dies alles, was wir tun müssen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5fb0009b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ee10ad21",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "91192540",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(\"def \", UInt16(3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a4ccf591",
+   "metadata": {},
+   "source": [
+    "Beachten Sie, dass die vollständig generische Form:\n",
+    "\n",
+    "```julia\n",
+    "mymult(x, y) = x * y\n",
+    "```\n",
+    "\n",
+    "tatsächlich eine Abkürzung für:\n",
+    "\n",
+    "```julia\n",
+    "mymult(x::Any, y::Any) = x * y\n",
+    "```\n",
+    "\n",
+    "ist."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ed16db85",
+   "metadata": {},
+   "source": [
+    "### Methoden\n",
+    "\n",
+    "In Julia werden verschiedene Versionen einer Funktion, die mit unterschiedlichen Arten von Argumenten arbeiten, als **Methoden** bezeichnet. Sie können die Liste der Methoden sehen, indem Sie dies ausführen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e52b3aee-2cbc-4cec-b131-204f9889b939",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(mymult)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c3f6143a",
+   "metadata": {},
+   "source": [
+    "Wenn Sie `methods` auf eine Kernfunktion wie `*` ausführen, erhalten Sie eine ziemlich lange Liste."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "de3e4302",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(*)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9bd61de6",
+   "metadata": {},
+   "source": [
+    "Das Makro `@which` kann verwendet werden, um herauszufinden, welche spezifische Methode gerade ausgeführt wird."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be365ee3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which \"Hello\"*\"World!\")\n",
+    "println(@which mymult(\"a\", 3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8fb4d5c3",
+   "metadata": {},
+   "source": [
+    "Wir können auch einige Methoden für `Animal`, `Dog` und `Cat` hinzufügen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bfb965d6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Define a generic function speak\n",
+    "speak(animal::Animal) = \"Some generic animal noise\"\n",
+    "# Use multiple dispatch to define method for specific types\n",
+    "speak(animal::Dog) = \"Woof! I am $(animal.name), a dog.\"\n",
+    "speak(animal::Cat) = \"Meow! I am $(animal.name), a cat.\"\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c8c559e0",
+   "metadata": {},
+   "source": [
+    "Dann testen wir es."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "14bda70c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show speak(a_dog)\n",
+    "@show speak(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "12b5d667",
+   "metadata": {},
+   "source": [
+    "### Abstrakte Typen und Mehrfachdispatch\n",
+    "\n",
+    "Um die Rolle abstrakter Typen im Mehrfachdispatch zu zeigen, kehren wir zu unserem Lebensmittelbeispiel zurück. Zunächst müssen wir die Hierarchie der Lebensmitteltypen klären. Dafür können wir abstrakte Typen verwenden."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bd602d8f-8074-4da6-9b05-1b9b091b7e43",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Food end\n",
+    "\n",
+    "abstract type Vegetables <: Food end\n",
+    "struct Potatoes <: Vegetables end\n",
+    "struct Carrots <: Vegetables end\n",
+    "\n",
+    "abstract type Fish <: Food end\n",
+    "struct Salmon <: Fish end\n",
+    "struct Shrimps <: Fish end # not biologically a fish, but we can consider them so from a culinary point of view\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3a9a5ca1-d549-49d9-8a91-6c9c58374efb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Food)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0e8f258e",
+   "metadata": {},
+   "source": [
+    "Nun haben wir ein Bratrezept gefunden, das für jede Art von Gemüse recht gut funktioniert. Dann schreiben wir so etwas wie:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c6e81e79-7d93-40bb-9e18-3c307c4ca8ea",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(vegetable::Vegetables)\n",
+    "    # make tempura\n",
+    "    # fry\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "517c0e6a",
+   "metadata": {},
+   "source": [
+    "Wenn wir unser Gemüse braten möchten, führen wir das folgendermaßen aus:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c9b3b8b9-91ba-4d30-8e71-dca2a478d4df",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "potatoes = Potatoes()\n",
+    "carrots = Carrots()\n",
+    "\n",
+    "println(@which frying(potatoes))\n",
+    "println(@which frying(carrots))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "694b2921",
+   "metadata": {},
+   "source": [
+    "Aber jetzt haben wir ein noch spezifischeres Rezept gefunden, das noch besser für Kartoffeln funktioniert. Was wir tun werden, ist das Schreiben einer neuen Funktion, die spezifisch für Kartoffeln ist."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "87cc4c94-1dc6-4515-8b23-135f7428ba1d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(potatoes::Potatoes)\n",
+    "    # directly fry in oil\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c4a8bcc4-c551-47d2-ab9f-bdf8e42142c4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which frying(potatoes))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7a6463aa",
+   "metadata": {},
+   "source": [
+    "Dieses Beispiel zeigt wirklich die Stärke von Julia. Mehrfachdispatch ist aus folgenden Gründen gut:\n",
+    "\n",
+    "- **Flexibilität:** Es ist möglich, neue Dinge auf sehr schnelle Weise auszuprobieren. Wir implementieren einen neuen Datentyp und verwenden die Methoden, die bereits für abstrakte Typen vorhanden sind.\n",
+    "- **Anpassungsfähigkeit:** Das Implementieren benutzerdefinierter Verhaltensweisen für unsere Datentypen ist einfach. Wir müssen einfach eine benutzerdefinierte Methode hinzufügen.\n",
+    "- **Effizienz:** Wir können die spezifischen Methoden so abstimmen, dass sie mit unserem spezifischen Datentyp super schnell sind."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0687c04d",
+   "metadata": {},
+   "source": [
+    "### Übung\n",
+    "Fügen Sie einige Methoden für `Animal`, `Dog` und `Cat` sowie andere konkrete Typen aus der letzten Übung hinzu."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6a45fa48",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4fb9457b",
+   "metadata": {},
+   "source": [
+    "## Dynamische Typisierung und Typableitung"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f27eca4d",
+   "metadata": {},
+   "source": [
+    "In der Programmiersprachen-Theorie fallen Typsysteme traditionell in zwei Kategorien. In **dynamisch typisierten** Sprachen wird der Typ eines Werts oder Ausdrucks nur zur Laufzeit abgeleitet, was in der Regel flexibleren Code ermöglicht. Beispiele hierfür sind Python oder MATLAB. Im Gegensatz dazu erfordern sogenannte **statisch typisierte** Sprachen (denken Sie an FORTRAN oder C++), dass die Typen bereits vor der Laufzeit bekannt sind, wenn das Programm kompiliert wird. Dies ermöglicht eine gründlichere Überprüfung auf Fehler (die sich in nicht übereinstimmenden Typen äußern können), und es führt normalerweise zu einer Leistungssteigerung, da mehr Informationen über die Speicherlayout des Programms zur Kompilierzeit bekannt sind. Als Ergebnis können Aspekte wie Vektorisierung, kontinuierliche Ausrichtung von Daten und Vorbelegung von Speicher leichter genutzt werden.\n",
+    "\n",
+    "Julia ist irgendwie beides."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3e5a5e29",
+   "metadata": {},
+   "source": [
+    "Julias starke Betonung von Typen ist einer der Gründe für ihre Leistungsfähigkeit und Flexibilität.\n",
+    "\n",
+    "Wenn der Code vor der Ausführung vorkompiliert wird, hat der Compiler Informationen über den Typ aller Variablen im Programm. Er wird dann die beste mögliche Methode für jede dieser Variablen suchen. Wenn eine spezifische und hoch effiziente Methode gefunden wird, wird diese verwendet. Wenn eine spezifische Methode fehlt, wird die nächste Möglichkeit im Typbaum verwendet, die immer noch funktioniert, wenn auch nicht so effizient.\n",
+    "\n",
+    "**Hinweis:** Diese Suche ist der Grund, warum es nicht möglich ist, abstrakte Typen zu instanziieren. Die Verwendung von Variablen abstrakter Typen würde diesen Vorgang unnötig kompliziert machen. Außerdem spiegelt es auch die Realität wider: Ein generisches Gemüse existiert physisch nicht, wir haben nur Kartoffeln, Karotten, Auberginen und so weiter."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "869f5fbc",
+   "metadata": {},
+   "source": [
+    "### Drei weitere Fakten über Julia-Typen:\n",
+    "- In Julia sind alle Typen gleich. Zum Beispiel gibt es keinen Unterschied zwischen `Int32` und `String`, obwohl der erste eine direkte Zuordnung zu Low-Level-Anweisungen in der CPU hat und der letztere nicht (im Gegensatz zu z.B. C++).\n",
+    "- Der Typ `Nothing` mit der einzigen Instanz `nothing` entspricht in Julia `void` in C++ oder `None` in Python. Er repräsentiert oft, dass eine Funktion nichts zurückgibt oder dass eine Variable nicht initialisiert wurde.\n",
+    "- `Any` ist die Wurzel des Typbaums: Jeder Typ in Julia ist ein Untertyp von `Any`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1ca89804",
+   "metadata": {},
+   "source": [
+    "##### Für weitere Details\n",
+    "https://docs.julialang.org/en/v1/manual/types/"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3e8c5b51-3ef6-4d40-9803-dcc2ab6eda3f",
+   "metadata": {},
+   "source": [
+    "## Übung\n",
+    "\n",
+    "Diese Übung befasst sich mit dem Schreiben eines benutzerdefinierten Datentyps für Polynome, einer Reihe von Konstruktoren dafür und Funktionen zum Durchführen von Operationen an ihnen. **Hinweis:** Das Schreiben benutzerdefinierter Polynome auf diese Weise ist weder effizient noch bequem, aber es ist ein gutes Beispiel für die bisher behandelten Themen.\n",
+    "\n",
+    "- Implementieren Sie einen neuen abstrakten Datentyp `AbstractPolynomial`. Dann implementieren Sie einige konkrete Untertypen, `PolynomialDegree0`, `PolynomialDegree1`, `PolynomialDegree2` und `PolynomialArbitraryDegree`. **Tipp:** Verwenden Sie eine Zuordnungstabelle, um die Koeffizienten auf konsistente Weise zwischen den verschiedenen Untertypen zu speichern: Sie können die Potenz von `x` als Schlüssel für den Koeffizientenwert verwenden.\n",
+    "\n",
+    "- Schreiben Sie einen Konstruktor für jeden von ihnen, der die Koeffizienten als Eingabe erhält. **Tipp:** Verwenden Sie `args::Real...`, um eine beliebige Anzahl von Argumenten zu sammeln. Die Angabe des Typs ist wichtig, um Unklarheiten beim Aufruf mit einem Argument zu vermeiden (versuchen Sie, `Real` nicht anzugeben, um das Problem zu sehen).\n",
+    "\n",
+    "- Schreiben Sie eine Funktion `Polynomial` mit mehreren Methoden: Abhängig von der Anzahl der Argumente sollte sie den richtigen Konstruktor aufrufen. **Tipps:** Sie können die Kurzform `Polynomial(c0) = PolynomialDegree0(c0)` verwenden, um den Code kürzer zu schreiben, und Sie sollten erneut `args...` verwenden, um eine beliebige Anzahl von Argumenten zu unterstützen.\n",
+    "\n",
+    "- Implementieren Sie eine Methode der `+` Funktion, die zwei `AbstractPolynomial` zusammenzählt. Es sollte mit jeder Kombination der konkreten Typen funktionieren. Es sollte auch eine Schleife über die Koeffizienten verwenden. **Hinweis:** Das Implementieren einer Methode für eine Funktion der Kernbibliothek erfordert zunächst das Importieren von `import Base.:+`.\n",
+    "\n",
+    "- Implementieren Sie einige weitere Methoden der `+`-Bibliothek, die Polynome niedriger Ordnung zusammenzählen. Diese sollten keine Schleife verwenden.\n",
+    "\n",
+    "- Zuletzt führen Sie eine Leistungsbewertung der `+` Funktion für zwei `PolynomialDegree1`-Polynome und für zwei `PolynomialArbitraryDegree`-Polynome vom Grad 1 durch. **Tipps:** Generieren Sie zufällige Koeffizienten mit `rand()`, wiederholen Sie den Vorgang einige Tausend Mal, um eine messbare Laufzeit zu erhalten, verwenden Sie die Makro `@time`, um die Zeit zu messen.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be3f23e1-b0f8-498f-afd2-9618cb64febb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Julia 1.9.3",
+   "language": "julia",
+   "name": "julia-1.9"
+  },
+  "language_info": {
+   "file_extension": ".jl",
+   "mimetype": "application/julia",
+   "name": "julia"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/3_Data_Types_Multiple_Dispatch_en.ipynb b/3_Data_Types_Multiple_Dispatch_en.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..07f2b7d094081e6b0fa490ae364c629a1591b24d
--- /dev/null
+++ b/3_Data_Types_Multiple_Dispatch_en.ipynb
@@ -0,0 +1,1180 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "24afcfe8-2739-4d92-a592-951071fa9df2",
+   "metadata": {},
+   "source": [
+    "## Constructors\n",
+    "\n",
+    "Before venturing forward in this notebook, we need a last ingredient from structs: custom constructors.\n",
+    "A constructor is a special function that is called to create an object. We take as an example a `Date` struct."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c171ec1a-902b-4211-b250-373953f4c58c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Date\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6857074a-eed4-4e56-9775-89779c37dfce",
+   "metadata": {},
+   "source": [
+    "We can instantiate a new date by invoking the default constructor."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "63630a93-6d93-489a-9656-9ac6b5be86f5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "date = Date(1, 11, 2023)\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "35369021-8d35-4f65-818f-feb94d3efdf7",
+   "metadata": {},
+   "source": [
+    "However, if we are not satisfied by the default behavior of the constructor, we can define our custom constructor which implements custom behavior. For example, we might be interested in a constructor which takes as input a string in the form `DD.MM.YYYY`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "34e84e19-734a-42c0-b263-2dc25128c0cd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function Date(str::String)\n",
+    "    parts = split(str, \".\")\n",
+    "    if length(parts) != 3\n",
+    "        throw(ArgumentError(\"Invalid date format. Use DD.MM.YYYY\"))\n",
+    "    end\n",
+    "\n",
+    "    d = parse(Int, parts[1])\n",
+    "    m = parse(Int, parts[2])\n",
+    "    y = parse(Int, parts[3])\n",
+    "\n",
+    "    return Date(d, m, y)\n",
+    "end\n",
+    "\n",
+    "date = Date(\"01.11.2023\")\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "90876390-1115-4b26-99b9-9221c15a1eb7",
+   "metadata": {},
+   "source": [
+    "Alternatively, constructors can be also defined inside the struct. The advantage of this strategy is that we are replacing the default constructor with custom behavior."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6dda7612-0cd2-471a-93e4-119a4e2f8f93",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct ValidatedDate\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "    function ValidatedDate(day, month, year)\n",
+    "        @assert(day > 0, \"day is negative\")\n",
+    "        @assert(month > 0, \"month is negative\")\n",
+    "        @assert(year > 0, \"year is negative\")\n",
+    "        new(day, month, year)\n",
+    "    end\n",
+    "end\n",
+    "\n",
+    "date = ValidatedDate(-1, 10, 2023)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e676156a-5f36-44b8-b8c1-95bf6d475e07",
+   "metadata": {},
+   "source": [
+    "### Exercises\n",
+    "\n",
+    "  - Write a custom constructor that takes the just the month and the year as input arguments and set the day to 1."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "884e42ed-d340-410c-ad42-950e173807c8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "48ce73e0-a9f2-48b8-974c-85147cce8d5f",
+   "metadata": {},
+   "source": [
+    "  - Modify the `ValidatedDate` inner constructor in such a way that it checks if the specified day exists in the given month. Assume that February has always 28 days."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eeb10ef4-0bb5-46e0-96b4-0fb98e4e1b3d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "063d5208-3297-4df4-8613-4d2c16a2d2c0",
+   "metadata": {},
+   "source": [
+    "  - Optional: modify the previous result and account also for leap years.\n",
+    "\n",
+    "    `Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the years 1600 and 2000 are.`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8b1dad51-d7cf-43aa-a2c8-9b3eaa4a1f4d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "debc3515-fa08-4577-82bb-9eced849d616",
+   "metadata": {},
+   "source": [
+    "# Data Types and Multiple Dispatch\n",
+    "\n",
+    "In this notebook, we will explore the fundamental concepts of Julia's type system and understand multiple dispatch, focusing on:\n",
+    "- Abstract and Concrete Types\n",
+    "- Dynamic Types\n",
+    "- Multiple Dispatch\n",
+    "\n",
+    "These concepts enhance Julia's performance, especially in mathematical and scientific computing.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7128c447",
+   "metadata": {},
+   "source": [
+    "## Abstract and concrete types"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "723212c2",
+   "metadata": {},
+   "source": [
+    "Before we discuss multiple dispatch of functions and dispatch by types, we briefly review Julia's type system. \n",
+    "Types in Julia fall into two categories: **Abstract** and **concrete**. \n",
+    "\n",
+    "- **Abstract Types**: Cannot be instantiated and serve to represent general categories of objects.\n",
+    "- **Concrete Types**: Can be instantiated and are used to create objects."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "58c7dc0c",
+   "metadata": {},
+   "source": [
+    "In Julia, there are several built-in functions related to querying and working with types. \n",
+    "Here is a selection of some important ones:\n",
+    "\n",
+    "- `<:`: The subtype operator used to check if a type is a subtype of another type.\n",
+    "- `isabstracttype(T)`: Check if T is an abstract type.\n",
+    "- `isconcretetype(T)`: Check if T is a concrete type.\n",
+    "- `subtypes(T)`: Get a list of all immediate subtypes of type T.\n",
+    "- `supertype(T)`: Get the direct supertype of type T."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "00533545",
+   "metadata": {},
+   "source": [
+    "Abstract types such as `Integer` or `Number` are supertypes of a bunch of other types, for example:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "50b00765",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show Int32 <: Integer   # Read:Int32 is a sub-type of Integer\n",
+    "@show UInt16 <: Integer  # UInt16 is a sub-type of Integer\n",
+    "@show Float32 <: Integer # Float32 is not a sub-type of Integer\n",
+    "@show Float32 <: Number  # Float32 is a sub-type of Number\n",
+    "@show Integer <: Number; # Integer is a sub-type of Nummber\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "329380e7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# by transitivity:\n",
+    "@show Int32  <: Number\n",
+    "@show UInt16 <: Number\n",
+    "@show Number <: Number;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "aa54bfef",
+   "metadata": {},
+   "source": [
+    "### Type properties\n",
+    "We can check type properties in various ways:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "49228132",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isconcretetype(Int32);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ffd1775",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Integer);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a778662d",
+   "metadata": {},
+   "source": [
+    "A fancy way is even to display a type tree:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f5a1fd53",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "    print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "\n",
+    "Print a tree structure that visualizes the type hierarchy rooted at `T`.\n",
+    "\n",
+    "# Parameters\n",
+    "- `T`: The type which serves as the root of the tree to print.\n",
+    "- `level`: (Optional, default=`0`) An integer specifying the current recursion depth - typically not provided by the user.\n",
+    "- `max_level`: (Optional, default=`typemax(Int)`) An integer specifying the maximum recursion depth, i.e., how many levels deep into the type hierarchy to print.\n",
+    "- `prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "- `subtype_prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "\n",
+    "# Usage\n",
+    "```julia\n",
+    "print_type_tree(Number, max_level=7)\n",
+    "```\n",
+    "\"\"\"\n",
+    "function print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "    # Stop recursion if max level is reached\n",
+    "    if level >= max_level\n",
+    "        return\n",
+    "    end\n",
+    "\n",
+    "    # Print current type\n",
+    "    println(prefix, subtype_prefix, T)\n",
+    "\n",
+    "    # Obtain subtypes\n",
+    "    subs = subtypes(T)\n",
+    "\n",
+    "    # Recursively print subtypes with adjusted prefix\n",
+    "    for (i, subtype) in enumerate(subs)\n",
+    "        new_prefix = \"    \"\n",
+    "        subtype_prefix = i < length(subs) ? \"├── \" : \"└── \"\n",
+    "\n",
+    "        # If the subtype has no further subtypes, avoid using the \"|\" in the prefix\n",
+    "        if isempty(subtypes(subtype))\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * \"    \", subtype_prefix)\n",
+    "        else\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix)\n",
+    "        end\n",
+    "    end\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "41f4de49",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Number)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c3722ed0",
+   "metadata": {},
+   "source": [
+    "**Exercise**: Write code to determine all subtypes of `Number`, whether they are abstract or concrete."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39b8339f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dacc835e",
+   "metadata": {},
+   "source": [
+    "To get the super type of a type, one can use `supertype`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e666712d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show supertype(Int64)\n",
+    "@show supertype(Signed)\n",
+    "@show supertype(Float16)\n",
+    "@show supertype(Float32)\n",
+    "@show supertype(Bool);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "147db9b0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function print_supertypes(T::Type)\n",
+    "    println(\"Supertypes of $T:\")\n",
+    "    while T != Any\n",
+    "        print(T, \" ---> \")\n",
+    "        T = supertype(T)\n",
+    "    end\n",
+    "    println(T)  # Print Any, which is the ultimate supertype of all types\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ed566f15",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Int64);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1462bc0c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Float64);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "808b7968",
+   "metadata": {},
+   "source": [
+    "### Custom Abstract And Concrete Types"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8cbad09b",
+   "metadata": {},
+   "source": [
+    "One can define custom abstract type by using `abstract type`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3ebf46f6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Animal end  # Abstract type\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c1cceba1",
+   "metadata": {},
+   "source": [
+    "Then, some concrete types can be created as well:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eaab065e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Dog <: Animal  # Concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n",
+    "\n",
+    "struct Cat <: Animal  # Another concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "747d5555",
+   "metadata": {},
+   "source": [
+    "In this example, we create two concrete animal, `Dog` and `Cat`. \n",
+    "One can use subtypes to obtain all subtypes of either an abstract type or a concrete type."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b8b7fca8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "subtypes(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "466cfcf7",
+   "metadata": {},
+   "source": [
+    "Again, using `isabstracttype` and `isconcretetype` we have"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3eced325",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Animal)\n",
+    "@show isabstracttype(Dog)\n",
+    "@show isabstracttype(Cat)\n",
+    "\n",
+    "@show isconcretetype(Animal)\n",
+    "@show isconcretetype(Dog)\n",
+    "@show isconcretetype(Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "580bf73c",
+   "metadata": {},
+   "source": [
+    "The type tree of `Animal` is:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "40debabd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f4fb75fe",
+   "metadata": {},
+   "source": [
+    "Now, we create two instances from the concrete types:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6f0f092f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a_dog = Dog(\"Buddy\", 3)\n",
+    "a_cat = Cat(\"Kitty\", 2)\n",
+    "\n",
+    "@show a_dog a_cat;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19797a26",
+   "metadata": {},
+   "source": [
+    "In Julia, the `isa` method is used to determine whether an instance is of a particular type, whether it is abstract or concrete:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "263461b1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isa(a_dog, Dog)\n",
+    "@show isa(a_dog, Animal)\n",
+    "@show isa(a_dog, Cat)\n",
+    "@show isa(a_cat, Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "cbb0a853",
+   "metadata": {},
+   "source": [
+    "The method `typeof` is used to obtain the type of an instance:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "208aed02",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show typeof(a_dog)\n",
+    "@show typeof(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1be97baf",
+   "metadata": {},
+   "source": [
+    "We can also get all supertypes of `Dog` and `Cat`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b3ca83e6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Dog)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "662a732f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Cat)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ff7efe19",
+   "metadata": {},
+   "source": [
+    "### Exercises\n",
+    "Write code to implement some abstract and concrete subtypes of `Animal`, and use `print_type_tree` to view the type tree. For example you can implement the abstract type `Bird` and then a few kinds of birds."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2fbfbb88",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "52b60c4e-1d3f-46f6-87a9-c3ab2917d8d0",
+   "metadata": {},
+   "source": [
+    "Which of the following type are subtypes of another?\n",
+    "Try to guess first and then verify by using the operator `<:`.\n",
+    "\n",
+    "```julia\n",
+    "Float64     AbstractFloat      Integer\n",
+    "Number      AbstractArray      Complex\n",
+    "Real        Any                Nothing\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "688afde5-1123-4adc-affd-687b966f387e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9efaf879",
+   "metadata": {},
+   "source": [
+    "In Julia concrete types are always a leaf of the type tree, i.e. they cannot be inherited from each other. For a C++ or Python person (as a few of us) this seems restrictive at first, but it takes away a lot of unnecessary complexity from the type hierarchy. We will not give further information now, the reason will be more clear at the end of this notebook.\n",
+    "\n",
+    "More details see: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "20a01368-ff73-4992-afae-792962aee09f",
+   "metadata": {},
+   "source": [
+    "## Multiple dispatch\n",
+    "\n",
+    "Multiple dispatch is probably the key feature of Julia that makes it different with respect to many other languages and give it the ability to be at the same time flexible and performant.\n",
+    "\n",
+    "To fully understand the concept of multiple dispatch, we will use an analogy, we will pretend that a programming language is a tool to write a book of food recipes. A recipe combines a method of cooking (for example baking, frying) with an ingredient (for example potatoes, carrots, fish).\n",
+    "\n",
+    "  - The first possibility is to organize the book according to methods of cooking: each chapter explains in detail a method of cooking. For example, we will have **Chapter 1: baking**, **Chapter 2: frying** and so on. The drawback of this approach is that whenever we add a new ingredient, we have to change multiple chapters. This approach, focused on the action rather than on ingredients, is typical of functional programming languages.\n",
+    "\n",
+    "  - The second possibility is to organize the book according to ingredients: each chapter focuses on one ingredient. For example, we will have **Chapter 1: potatoes**, **Chapter 2: fish** and so on. The drawback of this approach is that whenever we add a new recipe, we have again to change multiple chapters. This approach is focused on the ingredients (data) and it is typical of object-oriented programming, where we will have something like:\n",
+    "    ``` python\n",
+    "    class potatoes()\n",
+    "    potatoes.bake()\n",
+    "    potatoes.fry()\n",
+    "    ```\n",
+    "\n",
+    "\n",
+    "  - Julia takes a third approach called **multiple dispatch** which decouples the action from the data. In our hypothetical recipe book, we will have chapters like **Chapter 1: baking potatoes**, **Chapter 2: frying potatoes**, **Chapter 3: baking fish**, **Chapter 4: frying fish** and so on. Each of the chapters will contain something like:\n",
+    "    ``` julia\n",
+    "    function baking(potatoes::Potatoes)\n",
+    "    function frying(potatoes::Potatoes)\n",
+    "    function baking(fish::Fish)\n",
+    "    function frying(fish::Fish)\n",
+    "    ```\n",
+    "\n",
+    "    In this way, adding a new recipe for a specific kind of food does not require changing already written things. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a0a6aecb-bd29-472c-921b-c4135a09b026",
+   "metadata": {},
+   "source": [
+    "Let us return back to the `mymult` function and see how we can use the multiple dispatch to implement new functionality:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c43dc846",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(x, y) = x * y\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d27da057-cb24-45a2-b190-23ef6c8207fa",
+   "metadata": {},
+   "source": [
+    "We were able to safely use this functions with a number of type combinations, but some things do not yet work:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "65dbe501",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6757d995-cbc8-4c7b-be3a-fbdd9c8b984c",
+   "metadata": {},
+   "source": [
+    "Let's say we wanted to concatenate the string `str` $n$ times on multiplication with an integer $n$. In Julia this functionality is already implemented by the exponentiation operator:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8c18999b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"abc\"^4\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee1f8e46-3b4d-4f63-8e14-44ff70d7b5cf",
+   "metadata": {},
+   "source": [
+    "But for the sake of argument, assume we wanted `mymult(\"abc\", 4)` and `mymult(4, \"abc\")` to behave the same way. We define two special methods:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fa0af502",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(str::String, n::Integer) = str^n\n",
+    "mymult(n::Integer, str::String) = mymult(str, n)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "430aba46-e00f-4cef-9edb-062a9f8c5d77",
+   "metadata": {},
+   "source": [
+    "In both of these, the syntax `str::String` and `n::Integer` means that the respective method is only\n",
+    "considered during dispatch if the argument `str` is of type `String` and similarly `n` is an `Integer` (or subtype). Since Julia always dispatches to the most specific method in case multiple methods match, this is all we need to do:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5fb0009b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ee10ad21",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "91192540",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(\"def \", UInt16(3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "75814310-5d55-4f7b-8beb-903cb31366df",
+   "metadata": {},
+   "source": [
+    "Notice, that the fully generic\n",
+    "```julia\n",
+    "mymult(x, y) = x * y\n",
+    "```\n",
+    "is actually an abbreviation for\n",
+    "```julia\n",
+    "mymult(x::Any, y::Any) = x * y\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eb674aa1-2380-4ae5-b7b2-ad7579239def",
+   "metadata": {},
+   "source": [
+    "### Methods\n",
+    "\n",
+    "In Julia different version of a function, which work on different kinds of arguments are called **methods**. You can see the list of methods by running this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e52b3aee-2cbc-4cec-b131-204f9889b939",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(mymult)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c72d032a-1041-4dbd-9742-b2fa4c0ae86a",
+   "metadata": {},
+   "source": [
+    "If you run methods on a core function like `*`, you will get quite a long list."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "de3e4302",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(*)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b0f1b3da-885d-47b0-83b5-13039d88320e",
+   "metadata": {},
+   "source": [
+    "Using the macro `@which` can be used to discover which specific method is being run."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be365ee3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which \"Hello\"*\"World!\")\n",
+    "println(@which mymult(\"a\", 3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "49e0a478-3718-48bd-8370-029530b23a3f",
+   "metadata": {},
+   "source": [
+    "We can also add some method for `Animal`, `Dog`, and `Cat`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bfb965d6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Define a generic function speak\n",
+    "speak(animal::Animal) = \"Some generic animal noise\"\n",
+    "# Use multiple dispatch to define method for specific types\n",
+    "speak(animal::Dog) = \"Woof! I am $(animal.name), a dog.\"\n",
+    "speak(animal::Cat) = \"Meow! I am $(animal.name), a cat.\"\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ebeeb1e3-f7c0-4a0a-a3fa-539973ef0b36",
+   "metadata": {},
+   "source": [
+    "Then, let's test it."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "14bda70c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show speak(a_dog)\n",
+    "@show speak(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "790fc10a-4459-4b52-95f6-9718a31d4152",
+   "metadata": {},
+   "source": [
+    "### Abstract types and multiple dispatch\n",
+    "\n",
+    "To show the role of abstract types in multiple dispatch, we go back to our food example. First we need to clarify the hierarchy of food types. We can use abstract types for that."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bd602d8f-8074-4da6-9b05-1b9b091b7e43",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Food end\n",
+    "\n",
+    "abstract type Vegetables <: Food end\n",
+    "struct Potatoes <: Vegetables end\n",
+    "struct Carrots <: Vegetables end\n",
+    "\n",
+    "abstract type Fish <: Food end\n",
+    "struct Salmon <: Fish end\n",
+    "struct Shrimps <: Fish end # not biologically a fish, but we can consider them so from a culinary point of view\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3a9a5ca1-d549-49d9-8a91-6c9c58374efb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Food)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "508debf1-7ac1-44b3-b994-7ddc122dded8",
+   "metadata": {},
+   "source": [
+    "Now, we found a frying recipe which works decently for any kind of vegetable. Then we will write something like:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c6e81e79-7d93-40bb-9e18-3c307c4ca8ea",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(vegetable::Vegetables)\n",
+    "    # make tempura\n",
+    "    # fry\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8b67deab-9430-4eb1-968d-497065fca06e",
+   "metadata": {},
+   "source": [
+    "If we want to fry our vegetables, we will run"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c9b3b8b9-91ba-4d30-8e71-dca2a478d4df",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "potatoes = Potatoes()\n",
+    "carrots = Carrots()\n",
+    "\n",
+    "println(@which frying(potatoes))\n",
+    "println(@which frying(carrots))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7071213e-cc38-4fb2-a854-c5ebe0084a46",
+   "metadata": {},
+   "source": [
+    "But now we found an even more specific recipe that works even better for potatoes. What we will do is writing a new function which is specific for potatoes."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "87cc4c94-1dc6-4515-8b23-135f7428ba1d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(potatoes::Potatoes)\n",
+    "    # directly fry in oil\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c4a8bcc4-c551-47d2-ab9f-bdf8e42142c4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which frying(potatoes))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "db13beea-e3cc-4c6e-b996-d3f6c77fb171",
+   "metadata": {},
+   "source": [
+    "This example really shows the power of Julia. Multiple dispatch is good for these reasons:\n",
+    "  - **Flexibilty:** it is possible to try out new things in a very fast way. We implement a new data type and we use the methods which are already there for abstract types.\n",
+    "  - **Customization:** implementing custom behavior for our data types is easy, we simply need to add a custom method.\n",
+    "  - **Efficiency:** we can tune the specific methods to be super fast with our specific data type."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b39955b1-eed2-4c1f-bb61-fdcc0554a7b7",
+   "metadata": {},
+   "source": [
+    "### Exercise\n",
+    "Add some methods for `Animal`, `Dog` and `Cat` and other concrete type from the last exercise."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6a45fa48",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ccf57f28",
+   "metadata": {},
+   "source": [
+    "## Dynamical typing and type deduction"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "85870a09",
+   "metadata": {},
+   "source": [
+    "In programming language theory type systems traditionally fall in two categories.\n",
+    "In **dynamically typed** languages the type of a value or expression is inferred only at runtime,\n",
+    "which usually allows for more flexible code. Examples are Python or MATLAB. In contrast, so-called **statically-typed** languages (think FORTRAN or C++), require types to be already known before runtime when the program is compiled. This allows both to check more thoroughly for errors (which can manifest in mismatched types) and it usually brings a gain in performance because more things about the memory layout of the program is known at compile time. As a result, aspects such as vectorization, contiguous alignment of data, preallocation of memory can be leveraged more easily.\n",
+    "\n",
+    "Julia is kind of both."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a02c0a37",
+   "metadata": {},
+   "source": [
+    "Julia's strong emphasis on types is one of the reasons for its performance and flexibility.\n",
+    "\n",
+    "When the code is precompiled before execution, the compiler has the information about the type of all the variables in the program. It will then search the best possible method for each of those. If a specific and highly efficient one is found, that will be used. If a specific one is missing, it will use the next possibility in the type tree, which will still work even if not as efficiently.\n",
+    "\n",
+    "**Note:** this look up is the reason why it is not possible to instantiate abstract types, having variables of abstract types would make this operation unnecessary complicated. Moreover, it also reflects reality: a generic vegetable does not physically exist, we only have potatoes, carrots, eggplants and so on."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "27812692",
+   "metadata": {},
+   "source": [
+    "### Three more facts about Julia types:\n",
+    "- In Julia all types are the same. For example, there is no difference between `Int32` and `String`, even though the first has a direct mapping to low-level instructions in the CPU and the latter has not (contrast this with e.g. C++).\n",
+    "- The `Nothing` type with the single instance `nothing` is the Julia equivalent to `void` in C++ or `None` in Python. It often represents that a function does not return anything or that a variable has not been initialised.\n",
+    "- `Any` is the root of the type tree: Any type in Julia is a subtype of `Any`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ebc902af",
+   "metadata": {},
+   "source": [
+    "##### For more details\n",
+    "https://docs.julialang.org/en/v1/manual/types/"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bd273bc8-96ea-4260-93db-4934b7291965",
+   "metadata": {},
+   "source": [
+    "## Exercise\n",
+    "\n",
+    "This exercise is about writing a custom data type for polynomials, a set of constructors for them, and functions to perform operations on them. **Note:** writing custom polynomials in this way is neither efficient nor convenient, but it is a great example of the topics seen so far.\n",
+    "\n",
+    "  - Implement a new abstract type `AbstractPolynomial`. Then, implement a few concrete subtypes, `PolynomialDegree0`, `PolynomialDegree1`, `PolynomialDegree2` and `PolynomialArbitraryDegree`. **Tip:** use a dictionary to store the coefficients and do it in a consistent way between the various subtypes: you can use the power of `x` as the key for the coefficient value.\n",
+    "\n",
+    "  - Write a constructor for each of them which takes as input the coefficients. **Tip:** use `args::Real...` to gather an arbitrary number of arguments, specifying the type is important for avoiding ambiguity when calling with one argument (try not specyifing `Real` to see the problem).\n",
+    "\n",
+    "  - Write a function `Polynomial` with multiple methods: depending on the number of arguments, it should call the correct constructor. **Tips:** you can use the shorthand `Polynomial(c0) = PolynomialDegree0(c0)` to write more concise code, and you should again use `args...` to support an arbitrary amount of arguments.\n",
+    "\n",
+    "  - Implement a method of the `+` function which sums together two `AbstractPolynomial`. It should work with any combination of the concrete types. It should also use a loop over the coefficients. **Note:** implementing a method for a function of the core library requires first to import it `import Base.:+`.\n",
+    "\n",
+    "  - Implement some more methods of the `+` library which sum together polynomials of low degree, they should not use a for loop.\n",
+    "\n",
+    "  - Last, benchmark the `+` function for two `PolynomialDegree1` polynomials and for two `PolynomialArbitraryDegree` of degree 1. **Tips:** generate random coefficients using `rand()`, repeat the process a few thousands times to get a measurable run time, use the macro `@time` to get the time."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be3f23e1-b0f8-498f-afd2-9618cb64febb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Julia 1.9.3",
+   "language": "julia",
+   "name": "julia-1.9"
+  },
+  "language_info": {
+   "file_extension": ".jl",
+   "mimetype": "application/julia",
+   "name": "julia"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/3_Data_Types_Multiple_Dispatch.ipynb b/src/3_Data_Types_Multiple_Dispatch.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..fe5583e0e602b66850dd69608371431efe4e1585
--- /dev/null
+++ b/src/3_Data_Types_Multiple_Dispatch.ipynb
@@ -0,0 +1,1715 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "24afcfe8-2739-4d92-a592-951071fa9df2",
+   "metadata": {},
+   "source": [
+    "## Constructors\n",
+    "\n",
+    "Before venturing forward in this notebook, we need a last ingredient from structs: custom constructors.\n",
+    "A constructor is a special function that is called to create an object. We take as an example a `Date` struct."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f59de749-6cd1-4f77-b89e-57b110d83858",
+   "metadata": {},
+   "source": [
+    "## Konstruktoren\n",
+    "\n",
+    "Bevor wir in diesem Notebook weitermachen, benötigen wir noch eine letzte Zutat aus Strukturen: benutzerdefinierte Konstruktoren.\n",
+    "Ein Konstruktor ist eine spezielle Funktion, die aufgerufen wird, um ein Objekt zu erstellen. Wir nehmen als Beispiel eine `Datum` Struktur."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c171ec1a-902b-4211-b250-373953f4c58c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Date\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6857074a-eed4-4e56-9775-89779c37dfce",
+   "metadata": {},
+   "source": [
+    "We can instantiate a new date by invoking the default constructor."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c39605b9-2667-4c9d-8b1f-f6505baa0838",
+   "metadata": {},
+   "source": [
+    "Wir können ein neues Datum instanziieren, indem wir den Standardkonstruktor aufrufen."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "63630a93-6d93-489a-9656-9ac6b5be86f5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "date = Date(1, 11, 2023)\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "35369021-8d35-4f65-818f-feb94d3efdf7",
+   "metadata": {},
+   "source": [
+    "However, if we are not satisfied by the default behavior of the constructor, we can define our custom constructor which implements custom behavior. For example, we might be interested in a constructor which takes as input a string in the form `DD.MM.YYYY`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2cad81b4-b9df-42f3-a89b-8340036bbe8e",
+   "metadata": {},
+   "source": [
+    "Wenn wir jedoch mit dem Standardverhalten des Konstruktors nicht zufrieden sind, können wir unseren eigenen Konstruktor definieren, der ein benutzerdefiniertes Verhalten implementiert. Zum Beispiel könnten wir an einem Konstruktor interessiert sein, der eine Zeichenkette im Format `TT.MM.JJJJ` als Eingabe akzeptiert.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "34e84e19-734a-42c0-b263-2dc25128c0cd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function Date(str::String)\n",
+    "    parts = split(str, \".\")\n",
+    "    if length(parts) != 3\n",
+    "        throw(ArgumentError(\"Invalid date format. Use DD.MM.YYYY\"))\n",
+    "    end\n",
+    "\n",
+    "    d = parse(Int, parts[1])\n",
+    "    m = parse(Int, parts[2])\n",
+    "    y = parse(Int, parts[3])\n",
+    "\n",
+    "    return Date(d, m, y)\n",
+    "end\n",
+    "\n",
+    "date = Date(\"01.11.2023\")\n",
+    "@show date;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "90876390-1115-4b26-99b9-9221c15a1eb7",
+   "metadata": {},
+   "source": [
+    "Alternatively, constructors can be also defined inside the struct. The advantage of this strategy is that we are replacing the default constructor with custom behavior."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "915b71b1-c188-4c82-a2ff-810a58f39720",
+   "metadata": {},
+   "source": [
+    "Alternativ können Konstruktoren auch innerhalb der Struktur definiert werden. Der Vorteil dieser Strategie ist, dass wir den Standardkonstruktor durch benutzerdefiniertes Verhalten ersetzen."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6dda7612-0cd2-471a-93e4-119a4e2f8f93",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct ValidatedDate\n",
+    "    day::Integer\n",
+    "    month::Integer\n",
+    "    year::Integer\n",
+    "    function ValidatedDate(day, month, year)\n",
+    "        @assert(day > 0, \"day is negative\")\n",
+    "        @assert(month > 0, \"month is negative\")\n",
+    "        @assert(year > 0, \"year is negative\")\n",
+    "        new(day, month, year)\n",
+    "    end\n",
+    "end\n",
+    "\n",
+    "date = ValidatedDate(-1, 10, 2023)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e676156a-5f36-44b8-b8c1-95bf6d475e07",
+   "metadata": {},
+   "source": [
+    "### Exercises\n",
+    "\n",
+    "  - Write a custom constructor that takes the just the month and the year as input arguments and set the day to 1."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "89ab4f63-bd67-4cca-919a-f0d8f2bc88d1",
+   "metadata": {},
+   "source": [
+    "### Übungen\n",
+    "\n",
+    "  - Schreibe einen benutzerdefinierten Konstruktor, der nur den Monat und das Jahr als Eingabeargumente akzeptiert und den Tag auf 1 setzt."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "884e42ed-d340-410c-ad42-950e173807c8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "48ce73e0-a9f2-48b8-974c-85147cce8d5f",
+   "metadata": {},
+   "source": [
+    "  - Modify the `ValidatedDate` inner constructor in such a way that it checks if the specified day exists in the given month. Assume that February has always 28 days."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "459a7d50-1138-4fc1-af46-a0bdb4335287",
+   "metadata": {},
+   "source": [
+    "  - Passe den `ValidatedDate` inneren Konstruktor so an, dass er überprüft, ob der angegebene Tag im angegebenen Monat existiert. Nehmen Sie an, dass der Februar immer 28 Tage hat."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eeb10ef4-0bb5-46e0-96b4-0fb98e4e1b3d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "063d5208-3297-4df4-8613-4d2c16a2d2c0",
+   "metadata": {},
+   "source": [
+    "  - Optional: modify the previous result and account also for leap years.\n",
+    "\n",
+    "    `Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the years 1600 and 2000 are.`"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "169512ca-868f-406d-b8c8-8e9174d25b64",
+   "metadata": {},
+   "source": [
+    "  - Optional: Passe das vorherige Ergebnis an und berücksichtige auch Schaltjahre.\n",
+    "\n",
+    "    `Jedes Jahr, das genau durch vier teilbar ist, ist ein Schaltjahr, mit Ausnahme von Jahren, die genau durch 100 teilbar sind. Diese Jahrhundertjahre sind jedoch Schaltjahre, wenn sie genau durch 400 teilbar sind. Zum Beispiel sind die Jahre 1700, 1800 und 1900 keine Schaltjahre, aber die Jahre 1600 und 2000 sind es.`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8b1dad51-d7cf-43aa-a2c8-9b3eaa4a1f4d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "debc3515-fa08-4577-82bb-9eced849d616",
+   "metadata": {},
+   "source": [
+    "# Data Types and Multiple Dispatch\n",
+    "\n",
+    "In this notebook, we will explore the fundamental concepts of Julia's type system and understand multiple dispatch, focusing on:\n",
+    "- Abstract and Concrete Types\n",
+    "- Dynamic Types\n",
+    "- Multiple Dispatch\n",
+    "\n",
+    "These concepts enhance Julia's performance, especially in mathematical and scientific computing.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3ba487cb",
+   "metadata": {},
+   "source": [
+    "# Datentypen und Multiple Dispatch\n",
+    "\n",
+    "In diesem Arbeitsblatt werden wir die grundlegenden Konzepte von Julias Typsystem erforschen und Multiple Dispatch verstehen, wobei wir uns konzentrieren auf:\n",
+    "\n",
+    "- Abstrakte und Konkrete Typen\n",
+    "- Dynamische Typen\n",
+    "- Multiple Dispatch\n",
+    "\n",
+    "Diese Konzepte verbessern Julias Leistung, insbesondere bei mathematischer und wissenschaftlicher Datenverarbeitung."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7128c447",
+   "metadata": {},
+   "source": [
+    "## Abstract and concrete types"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fe56dbef",
+   "metadata": {},
+   "source": [
+    "## Abstrakte and konkrete Typen"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "723212c2",
+   "metadata": {},
+   "source": [
+    "Before we discuss multiple dispatch of functions and dispatch by types, we briefly review Julia's type system. \n",
+    "Types in Julia fall into two categories: **Abstract** and **concrete**. \n",
+    "\n",
+    "- **Abstract Types**: Cannot be instantiated and serve to represent general categories of objects.\n",
+    "- **Concrete Types**: Can be instantiated and are used to create objects."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "01604e4c",
+   "metadata": {},
+   "source": [
+    "Bevor wir den Multiple Dispatch von Funktionen und den Dispatch nach Typen besprechen, werfen wir kurz einen Blick auf Julias Typsystem. \n",
+    "Typen in Julia fallen in zwei Kategorien: **Abstrakt** und **konkret**.\n",
+    "\n",
+    "- **Abstrakte Typen**: Können nicht instanziiert werden und dienen dazu, allgemeine Kategorien von Objekten darzustellen.\n",
+    "- **Konkrete Typen**: Können instanziiert werden und werden verwendet, um Objekte zu erstellen.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "58c7dc0c",
+   "metadata": {},
+   "source": [
+    "In Julia, there are several built-in functions related to querying and working with types. \n",
+    "Here is a selection of some important ones:\n",
+    "\n",
+    "- `<:`: The subtype operator used to check if a type is a subtype of another type.\n",
+    "- `isabstracttype(T)`: Check if T is an abstract type.\n",
+    "- `isconcretetype(T)`: Check if T is a concrete type.\n",
+    "- `subtypes(T)`: Get a list of all immediate subtypes of type T.\n",
+    "- `supertype(T)`: Get the direct supertype of type T."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "41e57dbd",
+   "metadata": {},
+   "source": [
+    "In Julia gibt es mehrere eingebaute Funktionen, die sich auf das Abfragen und Arbeiten mit Typen beziehen.\n",
+    "Hier eine Auswahl einiger wichtiger:\n",
+    "\n",
+    "- `<:`: Der Subtyp-Operator, der verwendet wird, um zu überprüfen, ob ein Typ ein Subtyp eines anderen Typs ist.\n",
+    "- `isabstracttype(T)`: Überprüfen, ob T ein abstrakter Typ ist.\n",
+    "- `isconcretetype(T)`: Überprüfen, ob T ein konkreter Typ ist.\n",
+    "- `subtypes(T)`: Eine Liste aller unmittelbaren Subtypen des Typs T erhalten.\n",
+    "- `supertype(T)`: Den direkten Supertyp des Typs T erhalten.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "00533545",
+   "metadata": {},
+   "source": [
+    "Abstract types such as `Integer` or `Number` are supertypes of a bunch of other types, for example:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "da96b9b9",
+   "metadata": {},
+   "source": [
+    "Abstrakte Typen wie `Integer` oder `Number` sind Supertypen von vielen anderen Typen, zum Beispiel:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "50b00765",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show Int32 <: Integer   # Read:Int32 is a sub-type of Integer\n",
+    "@show UInt16 <: Integer  # UInt16 is a sub-type of Integer\n",
+    "@show Float32 <: Integer # Float32 is not a sub-type of Integer\n",
+    "@show Float32 <: Number  # Float32 is a sub-type of Number\n",
+    "@show Integer <: Number; # Integer is a sub-type of Nummber\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "329380e7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# by transitivity:\n",
+    "@show Int32  <: Number\n",
+    "@show UInt16 <: Number\n",
+    "@show Number <: Number;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "aa54bfef",
+   "metadata": {},
+   "source": [
+    "### Type properties\n",
+    "We can check type properties in various ways:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "027b76c8",
+   "metadata": {},
+   "source": [
+    "### Eigenschaften von Typen\n",
+    "Wir können Typ-Eigenschaften auf verschiedene Weisen überprüfen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "49228132",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isconcretetype(Int32);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ffd1775",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Integer);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a778662d",
+   "metadata": {},
+   "source": [
+    "A fancy way is even to display a type tree:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2a316d56",
+   "metadata": {},
+   "source": [
+    "Eine ausgefallene Möglichkeit ist sogar, einen Typ-Baum anzuzeigen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f5a1fd53",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "    print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "\n",
+    "Print a tree structure that visualizes the type hierarchy rooted at `T`.\n",
+    "\n",
+    "# Parameters\n",
+    "- `T`: The type which serves as the root of the tree to print.\n",
+    "- `level`: (Optional, default=`0`) An integer specifying the current recursion depth - typically not provided by the user.\n",
+    "- `max_level`: (Optional, default=`typemax(Int)`) An integer specifying the maximum recursion depth, i.e., how many levels deep into the type hierarchy to print.\n",
+    "- `prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "- `subtype_prefix`: (Optional, default=`\"\"`) A string used internally to format the tree structure - typically not provided by the user.\n",
+    "\n",
+    "# Usage\n",
+    "```julia\n",
+    "print_type_tree(Number, max_level=7)\n",
+    "```\n",
+    "\"\"\"\n",
+    "function print_type_tree(T, level=0, max_level=typemax(Int), prefix=\"\", subtype_prefix=\"\")\n",
+    "    # Stop recursion if max level is reached\n",
+    "    if level >= max_level\n",
+    "        return\n",
+    "    end\n",
+    "\n",
+    "    # Print current type\n",
+    "    println(prefix, subtype_prefix, T)\n",
+    "\n",
+    "    # Obtain subtypes\n",
+    "    subs = subtypes(T)\n",
+    "\n",
+    "    # Recursively print subtypes with adjusted prefix\n",
+    "    for (i, subtype) in enumerate(subs)\n",
+    "        new_prefix = \"    \"\n",
+    "        subtype_prefix = i < length(subs) ? \"├── \" : \"└── \"\n",
+    "\n",
+    "        # If the subtype has no further subtypes, avoid using the \"|\" in the prefix\n",
+    "        if isempty(subtypes(subtype))\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * \"    \", subtype_prefix)\n",
+    "        else\n",
+    "            print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix)\n",
+    "        end\n",
+    "    end\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "41f4de49",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Number)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c3722ed0",
+   "metadata": {},
+   "source": [
+    "**Exercise**: Write code to determine all subtypes of `Number`, whether they are abstract or concrete."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bc4662dc",
+   "metadata": {},
+   "source": [
+    "**Übung**: Schreiben Sie Code, um alle Subtypen von `Number` zu bestimmen, unabhängig davon, ob sie abstrakt oder konkret sind.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39b8339f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dacc835e",
+   "metadata": {},
+   "source": [
+    "To get the super type of a type, one can use `supertype`:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3ad36e86",
+   "metadata": {},
+   "source": [
+    "Um den Supertyp eines Typs zu erhalten, kann man `supertype` verwenden:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e666712d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show supertype(Int64)\n",
+    "@show supertype(Signed)\n",
+    "@show supertype(Float16)\n",
+    "@show supertype(Float32)\n",
+    "@show supertype(Bool);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "147db9b0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function print_supertypes(T::Type)\n",
+    "    println(\"Supertypes of $T:\")\n",
+    "    while T != Any\n",
+    "        print(T, \" ---> \")\n",
+    "        T = supertype(T)\n",
+    "    end\n",
+    "    println(T)  # Print Any, which is the ultimate supertype of all types\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ed566f15",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Int64);\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1462bc0c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Float64);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "808b7968",
+   "metadata": {},
+   "source": [
+    "### Custom Abstract And Concrete Types"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d16d0857",
+   "metadata": {},
+   "source": [
+    "### Benutzerdefinierte Abstrakte und Konkrete Typen\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8cbad09b",
+   "metadata": {},
+   "source": [
+    "One can define custom abstract type by using `abstract type`:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "882efff3",
+   "metadata": {},
+   "source": [
+    "Man kann benutzerdefinierte abstrakte Typen mit `abstract type` definieren:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3ebf46f6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Animal end  # Abstract type\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c1cceba1",
+   "metadata": {},
+   "source": [
+    "Then, some concrete types can be created as well:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d63c0261",
+   "metadata": {},
+   "source": [
+    "Dann können auch einige konkrete Typen erstellt werden:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eaab065e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "struct Dog <: Animal  # Concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n",
+    "\n",
+    "struct Cat <: Animal  # Another concrete type inheriting from Animal\n",
+    "    name::String\n",
+    "    age::Int\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "747d5555",
+   "metadata": {},
+   "source": [
+    "In this example, we create two concrete animal, `Dog` and `Cat`. \n",
+    "One can use subtypes to obtain all subtypes of either an abstract type or a concrete type."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "091a7905",
+   "metadata": {},
+   "source": [
+    "In diesem Beispiel erstellen wir zwei konkrete Tiere, `Dog` und `Cat`.\n",
+    "Man kann `subtypes` verwenden, um alle Subtypen eines abstrakten oder konkreten Typs zu erhalten.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b8b7fca8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "subtypes(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "466cfcf7",
+   "metadata": {},
+   "source": [
+    "Again, using `isabstracttype` and `isconcretetype` we have"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a2f3177d",
+   "metadata": {},
+   "source": [
+    "Wiederum können wir mit `isabstracttype` und `isconcretetype` überprüfen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3eced325",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isabstracttype(Animal)\n",
+    "@show isabstracttype(Dog)\n",
+    "@show isabstracttype(Cat)\n",
+    "\n",
+    "@show isconcretetype(Animal)\n",
+    "@show isconcretetype(Dog)\n",
+    "@show isconcretetype(Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "580bf73c",
+   "metadata": {},
+   "source": [
+    "The type tree of `Animal` is:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4b08f9f7",
+   "metadata": {},
+   "source": [
+    "Der Typ-Baum von `Animal` ist:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "40debabd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Animal)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f4fb75fe",
+   "metadata": {},
+   "source": [
+    "Now, we create two instances from the concrete types:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "87e2a153",
+   "metadata": {},
+   "source": [
+    "Jetzt erstellen wir zwei Instanzen von den konkreten Typen:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6f0f092f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a_dog = Dog(\"Buddy\", 3)\n",
+    "a_cat = Cat(\"Kitty\", 2)\n",
+    "\n",
+    "@show a_dog a_cat;\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19797a26",
+   "metadata": {},
+   "source": [
+    "In Julia, the `isa` method is used to determine whether an instance is of a particular type, whether it is abstract or concrete:\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "41ea3797",
+   "metadata": {},
+   "source": [
+    "In Julia wird die Methode `isa` verwendet, um zu bestimmen, ob eine Instanz von einem bestimmten Typ ist, egal ob er abstrakt oder konkret ist:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "263461b1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show isa(a_dog, Dog)\n",
+    "@show isa(a_dog, Animal)\n",
+    "@show isa(a_dog, Cat)\n",
+    "@show isa(a_cat, Cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "cbb0a853",
+   "metadata": {},
+   "source": [
+    "The method `typeof` is used to obtain the type of an instance:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee934bc9",
+   "metadata": {},
+   "source": [
+    "Die Methode `typeof` wird verwendet, um den Typ einer Instanz zu erhalten:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "208aed02",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show typeof(a_dog)\n",
+    "@show typeof(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1be97baf",
+   "metadata": {},
+   "source": [
+    "We can also get all supertypes of `Dog` and `Cat`:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8dc6aef6",
+   "metadata": {},
+   "source": [
+    "Wir können auch alle Supertypen von `Dog` und `Cat` erhalten:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b3ca83e6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Dog)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "662a732f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_supertypes(Cat)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ff7efe19",
+   "metadata": {},
+   "source": [
+    "### Exercises\n",
+    "Write code to implement some abstract and concrete subtypes of `Animal`, and use `print_type_tree` to view the type tree. For example you can implement the abstract type `Bird` and then a few kinds of birds."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d850444b",
+   "metadata": {},
+   "source": [
+    "### Übungen\n",
+    "Schreiben Sie Code, um einige abstrakte und konkrete Subtypen von `Animal` zu implementieren, und verwenden Sie `print_type_tree`, um den Typ-Baum anzusehen. Zum Beispiel können Sie den abstrakten Typ `Bird` implementieren und dann einige Arten von Vögeln.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2fbfbb88",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "52b60c4e-1d3f-46f6-87a9-c3ab2917d8d0",
+   "metadata": {},
+   "source": [
+    "Which of the following type are subtypes of another?\n",
+    "Try to guess first and then verify by using the operator `<:`.\n",
+    "\n",
+    "```julia\n",
+    "Float64     AbstractFloat      Integer\n",
+    "Number      AbstractArray      Complex\n",
+    "Real        Any                Nothing\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0add9f3d",
+   "metadata": {},
+   "source": [
+    "Welche der folgenden Typen sind Untertypen eines anderen? Versuchen Sie zuerst zu raten und überprüfen Sie dann mithilfe des Operators `<:`.\n",
+    "\n",
+    "```julia\n",
+    "Float64     AbstractFloat      Integer\n",
+    "Number      AbstractArray      Complex\n",
+    "Real        Any                Nothing\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "688afde5-1123-4adc-affd-687b966f387e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9efaf879",
+   "metadata": {},
+   "source": [
+    "In Julia concrete types are always a leaf of the type tree, i.e. they cannot be inherited from each other. For a C++ or Python person (as a few of us) this seems restrictive at first, but it takes away a lot of unnecessary complexity from the type hierarchy. We will not give further information now, the reason will be more clear at the end of this notebook.\n",
+    "\n",
+    "More details see: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "77bb1fe2",
+   "metadata": {},
+   "source": [
+    "In Julia sind konkrete Typen immer ein Blatt des Typbaums, d.h., sie können nicht voneinander abgeleitet werden. Für jemanden, der mit C++ oder Python vertraut ist (wie einige von uns), mag dies zunächst einschränkend erscheinen, aber es entfernt eine Menge unnötiger Komplexität aus der Typenhierarchie. Wir werden jetzt keine weiteren Informationen geben, der Grund wird am Ende dieses Notebooks klarer sein.\n",
+    "\n",
+    "Weitere Details finden Sie unter: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types).\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "20a01368-ff73-4992-afae-792962aee09f",
+   "metadata": {},
+   "source": [
+    "## Multiple dispatch\n",
+    "\n",
+    "Multiple dispatch is probably the key feature of Julia that makes it different with respect to many other languages and give it the ability to be at the same time flexible and performant.\n",
+    "\n",
+    "To fully understand the concept of multiple dispatch, we will use an analogy, we will pretend that a programming language is a tool to write a book of food recipes. A recipe combines a method of cooking (for example baking, frying) with an ingredient (for example potatoes, carrots, fish).\n",
+    "\n",
+    "  - The first possibility is to organize the book according to methods of cooking: each chapter explains in detail a method of cooking. For example, we will have **Chapter 1: baking**, **Chapter 2: frying** and so on. The drawback of this approach is that whenever we add a new ingredient, we have to change multiple chapters. This approach, focused on the action rather than on ingredients, is typical of functional programming languages.\n",
+    "\n",
+    "  - The second possibility is to organize the book according to ingredients: each chapter focuses on one ingredient. For example, we will have **Chapter 1: potatoes**, **Chapter 2: fish** and so on. The drawback of this approach is that whenever we add a new recipe, we have again to change multiple chapters. This approach is focused on the ingredients (data) and it is typical of object-oriented programming, where we will have something like:\n",
+    "    ``` python\n",
+    "    class potatoes()\n",
+    "    potatoes.bake()\n",
+    "    potatoes.fry()\n",
+    "    ```\n",
+    "\n",
+    "\n",
+    "  - Julia takes a third approach called **multiple dispatch** which decouples the action from the data. In our hypothetical recipe book, we will have chapters like **Chapter 1: baking potatoes**, **Chapter 2: frying potatoes**, **Chapter 3: baking fish**, **Chapter 4: frying fish** and so on. Each of the chapters will contain something like:\n",
+    "    ``` julia\n",
+    "    function baking(potatoes::Potatoes)\n",
+    "    function frying(potatoes::Potatoes)\n",
+    "    function baking(fish::Fish)\n",
+    "    function frying(fish::Fish)\n",
+    "    ```\n",
+    "\n",
+    "    In this way, adding a new recipe for a specific kind of food does not require changing already written things. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "64fc3a0b",
+   "metadata": {},
+   "source": [
+    "## Mehrfachdispatch (*multiple  dispatch*)\n",
+    "\n",
+    "Mehrfachdispatch ist wahrscheinlich das Schlüsselmerkmal von Julia, das es im Vergleich zu vielen anderen Sprachen unterscheidet und ihm die Fähigkeit verleiht, gleichzeitig flexibel und leistungsstark zu sein.\n",
+    "\n",
+    "Um das Konzept des Mehrfachdispatchs vollständig zu verstehen, werden wir eine Analogie verwenden. Wir werden so tun, als ob eine Programmiersprache ein Werkzeug zum Schreiben eines Kochbuches ist. Ein Rezept kombiniert eine Zubereitungsmethode (zum Beispiel Backen, Braten) mit einer Zutat (zum Beispiel Kartoffeln, Karotten, Fisch).\n",
+    "\n",
+    "- Die erste Möglichkeit besteht darin, das Buch nach Zubereitungsmethoden zu organisieren: Jedes Kapitel erklärt ausführlich eine Zubereitungsmethode. Zum Beispiel haben wir **Kapitel 1: Backen**, **Kapitel 2: Braten** und so weiter. Der Nachteil dieses Ansatzes ist, dass wir immer dann, wenn wir eine neue Zutat hinzufügen, mehrere Kapitel ändern müssen. Dieser Ansatz, der sich auf die Aktion und nicht auf die Zutaten konzentriert, ist typisch für funktionale Programmiersprachen.\n",
+    "\n",
+    "- Die zweite Möglichkeit besteht darin, das Buch nach Zutaten zu organisieren: Jedes Kapitel konzentriert sich auf eine Zutat. Zum Beispiel haben wir **Kapitel 1: Kartoffeln**, **Kapitel 2: Fisch** und so weiter. Der Nachteil dieses Ansatzes ist, dass wir immer dann, wenn wir ein neues Rezept hinzufügen, erneut mehrere Kapitel ändern müssen. Dieser Ansatz konzentriert sich auf die Zutaten (Daten) und ist typisch für objektorientierte Programmierung, bei der wir etwas Ähnliches haben könnten wie:\n",
+    "    ```julia\n",
+    "    struct Kartoffeln\n",
+    "        function backen()\n",
+    "        function braten()\n",
+    "    end\n",
+    "    struct Fisch\n",
+    "        function backen()\n",
+    "        function braten()\n",
+    "    end\n",
+    "    ```\n",
+    "\n",
+    "- Julia verfolgt einen dritten Ansatz namens **Mehrfachdispatch**, bei dem die Aktion von den Daten entkoppelt wird. In unserem hypothetischen Kochbuch haben wir Kapitel wie **Kapitel 1: Kartoffeln backen**, **Kapitel 2: Kartoffeln braten**, **Kapitel 3: Fisch backen**, **Kapitel 4: Fisch braten** und so weiter. Jedes dieser Kapitel enthält etwas Ähnliches wie:\n",
+    "    ```julia\n",
+    "    function backen(kartoffeln::Kartoffeln)\n",
+    "    function braten(kartoffeln::Kartoffeln)\n",
+    "    function backen(fisch::Fisch)\n",
+    "    function braten(fisch::Fisch) \n",
+    "    ```\n",
+    "\n",
+    "Auf diese Weise erfordert das Hinzufügen eines neuen Rezepts für eine bestimmte Art von Lebensmittel keine Änderungen an bereits geschriebenen Dingen.\n",
+    "\n",
+    "Quelle: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a0a6aecb-bd29-472c-921b-c4135a09b026",
+   "metadata": {},
+   "source": [
+    "Let us return back to the `mymult` function and see how we can use the multiple dispatch to implement new functionality:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0198d051",
+   "metadata": {},
+   "source": [
+    "Kehren wir zur `mymult`-Funktion zurück und sehen, wie wir den Mehrfachdispatch verwenden können, um neue Funktionen zu implementieren:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c43dc846",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(x, y) = x * y\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d27da057-cb24-45a2-b190-23ef6c8207fa",
+   "metadata": {},
+   "source": [
+    "We were able to safely use this functions with a number of type combinations, but some things do not yet work:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a088c5fd",
+   "metadata": {},
+   "source": [
+    "Wir konnten diese Funktionen sicher mit einer Vielzahl von Typenkombinationen verwenden, aber einige Dinge funktionieren noch nicht:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "65dbe501",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6757d995-cbc8-4c7b-be3a-fbdd9c8b984c",
+   "metadata": {},
+   "source": [
+    "Let's say we wanted to concatenate the string `str` $n$ times on multiplication with an integer $n$. In Julia this functionality is already implemented by the exponentiation operator:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1eda2f65",
+   "metadata": {},
+   "source": [
+    "Angenommen, wir möchten den String `str` $n$-mal aneinanderreihen, indem wir ihn mit einer ganzen Zahl $n$ multiplizieren. In Julia ist diese Funktionalität bereits durch den Potenzoperator implementiert:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8c18999b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"abc\"^4\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee1f8e46-3b4d-4f63-8e14-44ff70d7b5cf",
+   "metadata": {},
+   "source": [
+    "But for the sake of argument, assume we wanted `mymult(\"abc\", 4)` and `mymult(4, \"abc\")` to behave the same way. We define two special methods:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "13b4953e",
+   "metadata": {},
+   "source": [
+    "Aber der Argumentation halber nehmen wir an, wir möchten, dass `mymult(\"abc\", 4)` und `mymult(4, \"abc\")` auf die gleiche Weise funktionieren. Wir definieren zwei spezielle Methoden:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fa0af502",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(str::String, n::Integer) = str^n\n",
+    "mymult(n::Integer, str::String) = mymult(str, n)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "430aba46-e00f-4cef-9edb-062a9f8c5d77",
+   "metadata": {},
+   "source": [
+    "In both of these, the syntax `str::String` and `n::Integer` means that the respective method is only\n",
+    "considered during dispatch if the argument `str` is of type `String` and similarly `n` is an `Integer` (or subtype). Since Julia always dispatches to the most specific method in case multiple methods match, this is all we need to do:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ed42c2c2",
+   "metadata": {},
+   "source": [
+    "In beiden Fällen bedeutet die Syntax `str::String` und `n::Integer`, dass die jeweilige Methode nur während der Zuordnung berücksichtigt wird, wenn das Argument `str` vom Typ `String` ist und `n` ein `Integer` (oder Untertyp) ist. Da Julia immer zur spezifischsten Methode dispatcht, falls mehrere Methoden übereinstimmen, ist dies alles, was wir tun müssen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5fb0009b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ee10ad21",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(2, \" abc\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "91192540",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@which mymult(\"def \", UInt16(3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "75814310-5d55-4f7b-8beb-903cb31366df",
+   "metadata": {},
+   "source": [
+    "Notice, that the fully generic\n",
+    "```julia\n",
+    "mymult(x, y) = x * y\n",
+    "```\n",
+    "is actually an abbreviation for\n",
+    "```julia\n",
+    "mymult(x::Any, y::Any) = x * y\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a4ccf591",
+   "metadata": {},
+   "source": [
+    "Beachten Sie, dass die vollständig generische Form:\n",
+    "\n",
+    "```julia\n",
+    "mymult(x, y) = x * y\n",
+    "```\n",
+    "\n",
+    "tatsächlich eine Abkürzung für:\n",
+    "\n",
+    "```julia\n",
+    "mymult(x::Any, y::Any) = x * y\n",
+    "```\n",
+    "\n",
+    "ist."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eb674aa1-2380-4ae5-b7b2-ad7579239def",
+   "metadata": {},
+   "source": [
+    "### Methods\n",
+    "\n",
+    "In Julia different version of a function, which work on different kinds of arguments are called **methods**. You can see the list of methods by running this:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ed16db85",
+   "metadata": {},
+   "source": [
+    "### Methoden\n",
+    "\n",
+    "In Julia werden verschiedene Versionen einer Funktion, die mit unterschiedlichen Arten von Argumenten arbeiten, als **Methoden** bezeichnet. Sie können die Liste der Methoden sehen, indem Sie dies ausführen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e52b3aee-2cbc-4cec-b131-204f9889b939",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(mymult)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c72d032a-1041-4dbd-9742-b2fa4c0ae86a",
+   "metadata": {},
+   "source": [
+    "If you run methods on a core function like `*`, you will get quite a long list."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c3f6143a",
+   "metadata": {},
+   "source": [
+    "Wenn Sie `methods` auf eine Kernfunktion wie `*` ausführen, erhalten Sie eine ziemlich lange Liste."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "de3e4302",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "methods(*)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b0f1b3da-885d-47b0-83b5-13039d88320e",
+   "metadata": {},
+   "source": [
+    "Using the macro `@which` can be used to discover which specific method is being run."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9bd61de6",
+   "metadata": {},
+   "source": [
+    "Das Makro `@which` kann verwendet werden, um herauszufinden, welche spezifische Methode gerade ausgeführt wird."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be365ee3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which \"Hello\"*\"World!\")\n",
+    "println(@which mymult(\"a\", 3))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "49e0a478-3718-48bd-8370-029530b23a3f",
+   "metadata": {},
+   "source": [
+    "We can also add some method for `Animal`, `Dog`, and `Cat`:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8fb4d5c3",
+   "metadata": {},
+   "source": [
+    "Wir können auch einige Methoden für `Animal`, `Dog` und `Cat` hinzufügen:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bfb965d6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Define a generic function speak\n",
+    "speak(animal::Animal) = \"Some generic animal noise\"\n",
+    "# Use multiple dispatch to define method for specific types\n",
+    "speak(animal::Dog) = \"Woof! I am $(animal.name), a dog.\"\n",
+    "speak(animal::Cat) = \"Meow! I am $(animal.name), a cat.\"\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ebeeb1e3-f7c0-4a0a-a3fa-539973ef0b36",
+   "metadata": {},
+   "source": [
+    "Then, let's test it."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c8c559e0",
+   "metadata": {},
+   "source": [
+    "Dann testen wir es."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "14bda70c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show speak(a_dog)\n",
+    "@show speak(a_cat);\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "790fc10a-4459-4b52-95f6-9718a31d4152",
+   "metadata": {},
+   "source": [
+    "### Abstract types and multiple dispatch\n",
+    "\n",
+    "To show the role of abstract types in multiple dispatch, we go back to our food example. First we need to clarify the hierarchy of food types. We can use abstract types for that."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "12b5d667",
+   "metadata": {},
+   "source": [
+    "### Abstrakte Typen und Mehrfachdispatch\n",
+    "\n",
+    "Um die Rolle abstrakter Typen im Mehrfachdispatch zu zeigen, kehren wir zu unserem Lebensmittelbeispiel zurück. Zunächst müssen wir die Hierarchie der Lebensmitteltypen klären. Dafür können wir abstrakte Typen verwenden."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bd602d8f-8074-4da6-9b05-1b9b091b7e43",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abstract type Food end\n",
+    "\n",
+    "abstract type Vegetables <: Food end\n",
+    "struct Potatoes <: Vegetables end\n",
+    "struct Carrots <: Vegetables end\n",
+    "\n",
+    "abstract type Fish <: Food end\n",
+    "struct Salmon <: Fish end\n",
+    "struct Shrimps <: Fish end # not biologically a fish, but we can consider them so from a culinary point of view\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3a9a5ca1-d549-49d9-8a91-6c9c58374efb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print_type_tree(Food)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "508debf1-7ac1-44b3-b994-7ddc122dded8",
+   "metadata": {},
+   "source": [
+    "Now, we found a frying recipe which works decently for any kind of vegetable. Then we will write something like:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0e8f258e",
+   "metadata": {},
+   "source": [
+    "Nun haben wir ein Bratrezept gefunden, das für jede Art von Gemüse recht gut funktioniert. Dann schreiben wir so etwas wie:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c6e81e79-7d93-40bb-9e18-3c307c4ca8ea",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(vegetable::Vegetables)\n",
+    "    # make tempura\n",
+    "    # fry\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8b67deab-9430-4eb1-968d-497065fca06e",
+   "metadata": {},
+   "source": [
+    "If we want to fry our vegetables, we will run"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "517c0e6a",
+   "metadata": {},
+   "source": [
+    "Wenn wir unser Gemüse braten möchten, führen wir das folgendermaßen aus:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c9b3b8b9-91ba-4d30-8e71-dca2a478d4df",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "potatoes = Potatoes()\n",
+    "carrots = Carrots()\n",
+    "\n",
+    "println(@which frying(potatoes))\n",
+    "println(@which frying(carrots))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7071213e-cc38-4fb2-a854-c5ebe0084a46",
+   "metadata": {},
+   "source": [
+    "But now we found an even more specific recipe that works even better for potatoes. What we will do is writing a new function which is specific for potatoes."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "694b2921",
+   "metadata": {},
+   "source": [
+    "Aber jetzt haben wir ein noch spezifischeres Rezept gefunden, das noch besser für Kartoffeln funktioniert. Was wir tun werden, ist das Schreiben einer neuen Funktion, die spezifisch für Kartoffeln ist."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "87cc4c94-1dc6-4515-8b23-135f7428ba1d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function frying(potatoes::Potatoes)\n",
+    "    # directly fry in oil\n",
+    "end\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c4a8bcc4-c551-47d2-ab9f-bdf8e42142c4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "println(@which frying(potatoes))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "db13beea-e3cc-4c6e-b996-d3f6c77fb171",
+   "metadata": {},
+   "source": [
+    "This example really shows the power of Julia. Multiple dispatch is good for these reasons:\n",
+    "  - **Flexibilty:** it is possible to try out new things in a very fast way. We implement a new data type and we use the methods which are already there for abstract types.\n",
+    "  - **Customization:** implementing custom behavior for our data types is easy, we simply need to add a custom method.\n",
+    "  - **Efficiency:** we can tune the specific methods to be super fast with our specific data type."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7a6463aa",
+   "metadata": {},
+   "source": [
+    "Dieses Beispiel zeigt wirklich die Stärke von Julia. Mehrfachdispatch ist aus folgenden Gründen gut:\n",
+    "\n",
+    "- **Flexibilität:** Es ist möglich, neue Dinge auf sehr schnelle Weise auszuprobieren. Wir implementieren einen neuen Datentyp und verwenden die Methoden, die bereits für abstrakte Typen vorhanden sind.\n",
+    "- **Anpassungsfähigkeit:** Das Implementieren benutzerdefinierter Verhaltensweisen für unsere Datentypen ist einfach. Wir müssen einfach eine benutzerdefinierte Methode hinzufügen.\n",
+    "- **Effizienz:** Wir können die spezifischen Methoden so abstimmen, dass sie mit unserem spezifischen Datentyp super schnell sind."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b39955b1-eed2-4c1f-bb61-fdcc0554a7b7",
+   "metadata": {},
+   "source": [
+    "### Exercise\n",
+    "Add some methods for `Animal`, `Dog` and `Cat` and other concrete type from the last exercise."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0687c04d",
+   "metadata": {},
+   "source": [
+    "### Übung\n",
+    "Fügen Sie einige Methoden für `Animal`, `Dog` und `Cat` sowie andere konkrete Typen aus der letzten Übung hinzu."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6a45fa48",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ccf57f28",
+   "metadata": {},
+   "source": [
+    "## Dynamical typing and type deduction"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4fb9457b",
+   "metadata": {},
+   "source": [
+    "## Dynamische Typisierung und Typableitung"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "85870a09",
+   "metadata": {},
+   "source": [
+    "In programming language theory type systems traditionally fall in two categories.\n",
+    "In **dynamically typed** languages the type of a value or expression is inferred only at runtime,\n",
+    "which usually allows for more flexible code. Examples are Python or MATLAB. In contrast, so-called **statically-typed** languages (think FORTRAN or C++), require types to be already known before runtime when the program is compiled. This allows both to check more thoroughly for errors (which can manifest in mismatched types) and it usually brings a gain in performance because more things about the memory layout of the program is known at compile time. As a result, aspects such as vectorization, contiguous alignment of data, preallocation of memory can be leveraged more easily.\n",
+    "\n",
+    "Julia is kind of both."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f27eca4d",
+   "metadata": {},
+   "source": [
+    "In der Programmiersprachen-Theorie fallen Typsysteme traditionell in zwei Kategorien. In **dynamisch typisierten** Sprachen wird der Typ eines Werts oder Ausdrucks nur zur Laufzeit abgeleitet, was in der Regel flexibleren Code ermöglicht. Beispiele hierfür sind Python oder MATLAB. Im Gegensatz dazu erfordern sogenannte **statisch typisierte** Sprachen (denken Sie an FORTRAN oder C++), dass die Typen bereits vor der Laufzeit bekannt sind, wenn das Programm kompiliert wird. Dies ermöglicht eine gründlichere Überprüfung auf Fehler (die sich in nicht übereinstimmenden Typen äußern können), und es führt normalerweise zu einer Leistungssteigerung, da mehr Informationen über die Speicherlayout des Programms zur Kompilierzeit bekannt sind. Als Ergebnis können Aspekte wie Vektorisierung, kontinuierliche Ausrichtung von Daten und Vorbelegung von Speicher leichter genutzt werden.\n",
+    "\n",
+    "Julia ist irgendwie beides."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a02c0a37",
+   "metadata": {},
+   "source": [
+    "Julia's strong emphasis on types is one of the reasons for its performance and flexibility.\n",
+    "\n",
+    "When the code is precompiled before execution, the compiler has the information about the type of all the variables in the program. It will then search the best possible method for each of those. If a specific and highly efficient one is found, that will be used. If a specific one is missing, it will use the next possibility in the type tree, which will still work even if not as efficiently.\n",
+    "\n",
+    "**Note:** this look up is the reason why it is not possible to instantiate abstract types, having variables of abstract types would make this operation unnecessary complicated. Moreover, it also reflects reality: a generic vegetable does not physically exist, we only have potatoes, carrots, eggplants and so on."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3e5a5e29",
+   "metadata": {},
+   "source": [
+    "Julias starke Betonung von Typen ist einer der Gründe für ihre Leistungsfähigkeit und Flexibilität.\n",
+    "\n",
+    "Wenn der Code vor der Ausführung vorkompiliert wird, hat der Compiler Informationen über den Typ aller Variablen im Programm. Er wird dann die beste mögliche Methode für jede dieser Variablen suchen. Wenn eine spezifische und hoch effiziente Methode gefunden wird, wird diese verwendet. Wenn eine spezifische Methode fehlt, wird die nächste Möglichkeit im Typbaum verwendet, die immer noch funktioniert, wenn auch nicht so effizient.\n",
+    "\n",
+    "**Hinweis:** Diese Suche ist der Grund, warum es nicht möglich ist, abstrakte Typen zu instanziieren. Die Verwendung von Variablen abstrakter Typen würde diesen Vorgang unnötig kompliziert machen. Außerdem spiegelt es auch die Realität wider: Ein generisches Gemüse existiert physisch nicht, wir haben nur Kartoffeln, Karotten, Auberginen und so weiter."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "27812692",
+   "metadata": {},
+   "source": [
+    "### Three more facts about Julia types:\n",
+    "- In Julia all types are the same. For example, there is no difference between `Int32` and `String`, even though the first has a direct mapping to low-level instructions in the CPU and the latter has not (contrast this with e.g. C++).\n",
+    "- The `Nothing` type with the single instance `nothing` is the Julia equivalent to `void` in C++ or `None` in Python. It often represents that a function does not return anything or that a variable has not been initialised.\n",
+    "- `Any` is the root of the type tree: Any type in Julia is a subtype of `Any`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "869f5fbc",
+   "metadata": {},
+   "source": [
+    "### Drei weitere Fakten über Julia-Typen:\n",
+    "- In Julia sind alle Typen gleich. Zum Beispiel gibt es keinen Unterschied zwischen `Int32` und `String`, obwohl der erste eine direkte Zuordnung zu Low-Level-Anweisungen in der CPU hat und der letztere nicht (im Gegensatz zu z.B. C++).\n",
+    "- Der Typ `Nothing` mit der einzigen Instanz `nothing` entspricht in Julia `void` in C++ oder `None` in Python. Er repräsentiert oft, dass eine Funktion nichts zurückgibt oder dass eine Variable nicht initialisiert wurde.\n",
+    "- `Any` ist die Wurzel des Typbaums: Jeder Typ in Julia ist ein Untertyp von `Any`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ebc902af",
+   "metadata": {},
+   "source": [
+    "##### For more details\n",
+    "https://docs.julialang.org/en/v1/manual/types/"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1ca89804",
+   "metadata": {},
+   "source": [
+    "##### Für weitere Details\n",
+    "https://docs.julialang.org/en/v1/manual/types/"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bd273bc8-96ea-4260-93db-4934b7291965",
+   "metadata": {},
+   "source": [
+    "## Exercise\n",
+    "\n",
+    "This exercise is about writing a custom data type for polynomials, a set of constructors for them, and functions to perform operations on them. **Note:** writing custom polynomials in this way is neither efficient nor convenient, but it is a great example of the topics seen so far.\n",
+    "\n",
+    "  - Implement a new abstract type `AbstractPolynomial`. Then, implement a few concrete subtypes, `PolynomialDegree0`, `PolynomialDegree1`, `PolynomialDegree2` and `PolynomialArbitraryDegree`. **Tip:** use a dictionary to store the coefficients and do it in a consistent way between the various subtypes: you can use the power of `x` as the key for the coefficient value.\n",
+    "\n",
+    "  - Write a constructor for each of them which takes as input the coefficients. **Tip:** use `args::Real...` to gather an arbitrary number of arguments, specifying the type is important for avoiding ambiguity when calling with one argument (try not specyifing `Real` to see the problem).\n",
+    "\n",
+    "  - Write a function `Polynomial` with multiple methods: depending on the number of arguments, it should call the correct constructor. **Tips:** you can use the shorthand `Polynomial(c0) = PolynomialDegree0(c0)` to write more concise code, and you should again use `args...` to support an arbitrary amount of arguments.\n",
+    "\n",
+    "  - Implement a method of the `+` function which sums together two `AbstractPolynomial`. It should work with any combination of the concrete types. It should also use a loop over the coefficients. **Note:** implementing a method for a function of the core library requires first to import it `import Base.:+`.\n",
+    "\n",
+    "  - Implement some more methods of the `+` library which sum together polynomials of low degree, they should not use a for loop.\n",
+    "\n",
+    "  - Last, benchmark the `+` function for two `PolynomialDegree1` polynomials and for two `PolynomialArbitraryDegree` of degree 1. **Tips:** generate random coefficients using `rand()`, repeat the process a few thousands times to get a measurable run time, use the macro `@time` to get the time."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3e8c5b51-3ef6-4d40-9803-dcc2ab6eda3f",
+   "metadata": {},
+   "source": [
+    "## Übung\n",
+    "\n",
+    "Diese Übung befasst sich mit dem Schreiben eines benutzerdefinierten Datentyps für Polynome, einer Reihe von Konstruktoren dafür und Funktionen zum Durchführen von Operationen an ihnen. **Hinweis:** Das Schreiben benutzerdefinierter Polynome auf diese Weise ist weder effizient noch bequem, aber es ist ein gutes Beispiel für die bisher behandelten Themen.\n",
+    "\n",
+    "- Implementieren Sie einen neuen abstrakten Datentyp `AbstractPolynomial`. Dann implementieren Sie einige konkrete Untertypen, `PolynomialDegree0`, `PolynomialDegree1`, `PolynomialDegree2` und `PolynomialArbitraryDegree`. **Tipp:** Verwenden Sie eine Zuordnungstabelle, um die Koeffizienten auf konsistente Weise zwischen den verschiedenen Untertypen zu speichern: Sie können die Potenz von `x` als Schlüssel für den Koeffizientenwert verwenden.\n",
+    "\n",
+    "- Schreiben Sie einen Konstruktor für jeden von ihnen, der die Koeffizienten als Eingabe erhält. **Tipp:** Verwenden Sie `args::Real...`, um eine beliebige Anzahl von Argumenten zu sammeln. Die Angabe des Typs ist wichtig, um Unklarheiten beim Aufruf mit einem Argument zu vermeiden (versuchen Sie, `Real` nicht anzugeben, um das Problem zu sehen).\n",
+    "\n",
+    "- Schreiben Sie eine Funktion `Polynomial` mit mehreren Methoden: Abhängig von der Anzahl der Argumente sollte sie den richtigen Konstruktor aufrufen. **Tipps:** Sie können die Kurzform `Polynomial(c0) = PolynomialDegree0(c0)` verwenden, um den Code kürzer zu schreiben, und Sie sollten erneut `args...` verwenden, um eine beliebige Anzahl von Argumenten zu unterstützen.\n",
+    "\n",
+    "- Implementieren Sie eine Methode der `+` Funktion, die zwei `AbstractPolynomial` zusammenzählt. Es sollte mit jeder Kombination der konkreten Typen funktionieren. Es sollte auch eine Schleife über die Koeffizienten verwenden. **Hinweis:** Das Implementieren einer Methode für eine Funktion der Kernbibliothek erfordert zunächst das Importieren von `import Base.:+`.\n",
+    "\n",
+    "- Implementieren Sie einige weitere Methoden der `+`-Bibliothek, die Polynome niedriger Ordnung zusammenzählen. Diese sollten keine Schleife verwenden.\n",
+    "\n",
+    "- Zuletzt führen Sie eine Leistungsbewertung der `+` Funktion für zwei `PolynomialDegree1`-Polynome und für zwei `PolynomialArbitraryDegree`-Polynome vom Grad 1 durch. **Tipps:** Generieren Sie zufällige Koeffizienten mit `rand()`, wiederholen Sie den Vorgang einige Tausend Mal, um eine messbare Laufzeit zu erhalten, verwenden Sie die Makro `@time`, um die Zeit zu messen.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be3f23e1-b0f8-498f-afd2-9618cb64febb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here.\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Julia 1.9.3",
+   "language": "julia",
+   "name": "julia-1.9"
+  },
+  "language_info": {
+   "file_extension": ".jl",
+   "mimetype": "application/julia",
+   "name": "julia"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}