{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3b9e97dc",
   "metadata": {},
   "source": [
    "# Ein Fallbeispiel: Der parametrisierte Datentyp PComplex\n",
    "\n",
    "Wir wollen als neuen numerischen Typen **komplexe Zahlen in Polardarstellung $z=r e^{i\\phi}=(r,ϕ)$** einführen. \n",
    "\n",
    "- Der Typ soll sich in die Typhierarchie einfügen als Subtyp von 'Number'.\n",
    "- $r$ und $\\phi$ sollen Gleitkommazahlen sein. (Im Unterschied zu komplexen Zahlen in 'kartesischen' Koordinaten hat eine Einschränkung auf ganzzahlige Werte von r oder ϕ mathematisch wenig Sinn.)\n",
    "\n",
    "## Die Definition von `PComplex`\n",
    "\n",
    "Ein erster Versuch könnte so aussehen:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b00fa817",
   "metadata": {},
   "outputs": [],
   "source": [
    "struct PComplex1{T <: AbstractFloat} <: Number\n",
    "    r :: T\n",
    "    ϕ :: T\n",
    "end\n",
    "\n",
    "z1 = PComplex1(-32.0, 33.0)\n",
    "z2 = PComplex1{Float32}(12, 13)\n",
    "@show z1 z2;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00603f51",
   "metadata": {},
   "source": [
    "Julia stellt automatisch *default constructors* zur Verfügung:\n",
    "\n",
    "- den Konstruktor `PComplex1`, bei dem der Typ `T` von den übergebenen Argumenten abgeleitet wird und\n",
    "- Konstruktoren `PComplex{Float64},...` mit expliziter Typangabe. Hier wird versucht, die Argumente in den angeforderten Typ zu konvertieren. \n",
    "\n",
    "------\n",
    "\n",
    "Wir wollen nun, dass der Konstruktor noch mehr tut. \n",
    "In der Polardarstellung soll $0\\le r$ und $0\\le \\phi<2\\pi$ gelten.\n",
    "\n",
    "Wenn die übergebenen Argumente das nicht erfüllen, sollten sie entsprechend umgerechnet werden.\n",
    "\n",
    "Dazu definieren wir einen _inner constructor_, der den _default constructor_ ersetzt.\n",
    "\n",
    "- Ein _inner constructor_ ist eine Funktion innerhalb der `struct`-Definition.\n",
    "- In einem _inner constructor_ kann man die spezielle Funktion `new` verwenden, die wie der _default constructor_ wirkt.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d583f845",
   "metadata": {},
   "outputs": [],
   "source": [
    "struct PComplex{T <: AbstractFloat} <: Number\n",
    "    r :: T\n",
    "    ϕ :: T\n",
    "\n",
    "    function PComplex{T}(r::T, ϕ::T) where T<:AbstractFloat\n",
    "        if r<0            # flip the sign of r and correct phi\n",
    "            r = -r\n",
    "            ϕ += π\n",
    "        end\n",
    "        if r==0 ϕ=0 end  # normalize r=0 case to phi=0 \n",
    "        ϕ = mod(ϕ, 2π)   # map phi into interval [0,2pi)\n",
    "        new(r, ϕ)        # new() ist special function,\n",
    "    end                  #   available only inside inner constructors\n",
    "\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3d6ada4",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| echo: false\n",
    "#| output: false\n",
    "\n",
    "#=\n",
    "in den ganzen quarto-runs wollen bir hier noch das default-show benutzen\n",
    "=#\n",
    "zz = @which Base.show(stdout, PComplex{Float64}(2.,3.))\n",
    "if zz.module != Base\n",
    "    Base.delete_method(zz)\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55f6b7dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "z1 = PComplex{Float64}(-3.3, 7π+1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99f6d6e1",
   "metadata": {},
   "source": [
    "Für die explizite Angabe eines *inner constructors*  müssen wir allerdings einen Preis zahlen: Die sonst von Julia bereitgestellten *default constructors* fehlen.  \n",
    "\n",
    "Den Konstruktor, der  ohne explizite Typangabe in geschweiften Klammern auskommt und den Typ der Argumente übernimmt, wollen wir gerne auch haben:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6c36f18",
   "metadata": {},
   "outputs": [],
   "source": [
    "PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)\n",
    "\n",
    "z2 = PComplex(2.0, 0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d77812c",
   "metadata": {},
   "source": [
    "## Eine neue Schreibweise\n",
    "\n",
    "Julia verwendet `//` als Infix-Konstruktor für den Typ `Rational`. Sowas Schickes wollen wir auch.  \n",
    "\n",
    "In der Elektronik/Elektrotechnik werden [Wechselstromgrößen durch komplexe Zahlen beschrieben.](https://de.wikipedia.org/wiki/Komplexe_Wechselstromrechnung). Dabei ist eine Darstellung komplexer Zahlen durch \"Betrag\" und \"Phase\" üblich und sie wird gerne in der sogenannten [Versor-Form](https://de.wikipedia.org/wiki/Versor)  (engl. *phasor*) dargestellt:\n",
    "$$\n",
    " z= r\\enclose{phasorangle}{\\phi} = 3.4\\;\\enclose{phasorangle}{45^\\circ}\n",
    "$$\n",
    "wobei man in der Regel den Winkel in Grad notiert.  \n",
    "\n",
    ":::{.callout-note .titlenormal collapse=\"true\"}\n",
    "\n",
    "## Mögliche Infix-Operatoren in Julia\n",
    "\n",
    "In Julia ist eine große Anzahl von Unicode-Zeichen reserviert für die Verwendung als Operatoren. Die definitive Liste ist im [Quellcode des Parsers.](https://github.com/JuliaLang/julia/blob/eaa2c58aeb12f27c1d8c116ab111773a4fc4495f/src/julia-parser.scm#L13-L31)\n",
    "\n",
    "Auf Details  werden wir in einem späteren Kapitel noch eingehen. \n",
    "\n",
    "Und ja, der Julia-Parser ist in einem Lisp(genauer: Scheme)-Dialekt geschrieben. In Julia ist ein kleiner Scheme-Interpreter namens [femtolisp](https://github.com/JeffBezanson/femtolisp) integriert. Geschrieben hat ihn einer der \"Väter\" von Julia bevor er mit der Arbeit an Julia begann.  \n",
    "\n",
    ":::\n",
    "\n",
    "Das Winkel-Zeichen `∠` steht leider nicht als Operatorsymbol zur Verfügung. Wir weichen aus auf `⋖`. Das kann in Julia als  als `\\lessdot<tab>` eingegeben werden.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4fc6d3c",
   "metadata": {},
   "outputs": [],
   "source": [
    " ⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)\n",
    "\n",
    "z3 = 2. ⋖ 90."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c19f6e25",
   "metadata": {},
   "source": [
    "(Die Typ-Annotation -- `Real` statt `AbstractFloat` -- ist ein Vorgriff auf kommende weitere Konstruktoren. Im Moment funktioniert der Operator `⋖` erstmal nur mit `Float`s.)\n",
    "\n",
    "\n",
    "Natürlich wollen wir auch die Ausgabe so schön haben. Details dazu findet man in der [Dokumentation](https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13e750ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "using Printf\n",
    "\n",
    "function Base.show(io::IO, z::PComplex)\n",
    "    # wir drucken die Phase in Grad, auf Zehntelgrad gerundet, \n",
    "    p = z.ϕ * 180/π\n",
    "    sp = @sprintf \"%.1f\" p\n",
    "    print(io, z.r, \"⋖\", sp, '°')\n",
    "end\n",
    "\n",
    "@show z3;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1c7d6fa",
   "metadata": {},
   "source": [
    "## Methoden für `PComplex`\n",
    "\n",
    "Damit unser Typ ein anständiges Mitglied der von `Number` abstammenden Typfamilie wird, brauchen wir allerdings noch eine ganze Menge mehr. Es müssen Arithmetik, Vergleichsoperatoren, Konvertierungen usw. definiert werden. \n",
    "\n",
    "\n",
    "Wir beschränken uns auf  Multiplikation und Quadratwurzeln.\n",
    "\n",
    "\n",
    ":::{.callout-note collapse=\"true\"}\n",
    "## Module \n",
    "\n",
    "- Um die `methods` der existierenden Funktionen und Operationen zu ergänzen, muss man diese mit ihrem 'vollen Namen' ansprechen.\n",
    "- Alle Objekte gehören zu einem Namensraum oder `module`.\n",
    "- Die meisten Basisfunktionen gehören zum Modul `Base`, welches standardmäßig immer ohne explizites `using ...` geladen wird. \n",
    "- Solange man keine eigenen Module definiert, sind die eigenen Definitionen im Modul `Main`.\n",
    "- Das Macro `@which`, angewendet auf einen Namen, zeigt an, in welchem Modul der Name definiert wurde. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "373829e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "f(x) = 3x^3\n",
    "@which f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4490292",
   "metadata": {},
   "outputs": [],
   "source": [
    "wp = @which +\n",
    "ws = @which(sqrt)\n",
    "println(\"Modul für Addition: $wp, Modul für sqrt: $ws\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d49ec514",
   "metadata": {},
   "source": [
    ":::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2186aeb",
   "metadata": {},
   "outputs": [],
   "source": [
    "qwurzel(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d93d3ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| echo: false\n",
    "#| output: false\n",
    "\n",
    "#=\n",
    "damit das length(methods(sqrt)) klappt\n",
    "=#\n",
    "if hasmethod(sqrt, (PComplex,))\n",
    "    zz = @which Base.sqrt(PComplex{Float64}(1.,1.))\n",
    "    Base.delete_method(zz)\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07ed8595",
   "metadata": {},
   "source": [
    "Die Funktion `sqrt()` hat schon einige Methoden:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ecfd1ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "length(methods(sqrt))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f619c771",
   "metadata": {},
   "source": [
    "Jetzt wird es eine Methode mehr:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56a4ce0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "Base.sqrt(z::PComplex) = qwurzel(z)\n",
    "\n",
    "length(methods(sqrt))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "195204e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "sqrt(z2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c912bcd2",
   "metadata": {},
   "source": [
    "und nun zur Multiplikation:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c648a2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)\n",
    "\n",
    "@show z1 * z2;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79074b01",
   "metadata": {},
   "source": [
    "(Da das Operatorsymbol kein normaler Name ist, muss der Doppelpunkt bei der Zusammensetzung mit `Base.` sein.)\n",
    "\n",
    "Wir können allerdings noch nicht mit anderen numerischen Typen multiplizieren. Dazu könnte man nun eine Vielzahl von entsprechenden Methoden definieren. Julia stellt *für numerische Typen* noch einen weiteren Mechanismus zur Verfügung, der das etwas vereinfacht.\n",
    "\n",
    "\n",
    "## Typ-Promotion und Konversion \n",
    "\n",
    "In Julia kann man bekanntlich die verschiedensten numerischen Typen nebeneinander verwenden.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff3ee027",
   "metadata": {},
   "outputs": [],
   "source": [
    "1//3 + 5 + 5.2 + 0xff"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f43ef8c6",
   "metadata": {},
   "source": [
    "Wenn man in die zahlreichen Methoden schaut, die z.B. für `+` und `*` definiert sind, findet man u.a. eine Art 'catch-all-Definition'\n",
    "\n",
    "```julia\n",
    "+(x::Number, y::Number) = +(promote(x,y)...)\n",
    "*(x::Number, y::Number) = *(promote(x,y)...)\n",
    "```\n",
    "\n",
    "\n",
    "\n",
    "(Die 3 Punkte sind der splat-Operator, der das von promote() zurückgegebene Tupel wieder in seine Bestandteile zerlegt.)\n",
    "\n",
    "Da die Methode mit den Typen `(Number, Number)` sehr allgemein ist, wird sie erst verwendet, wenn spezifischere Methoden nicht greifen.\n",
    "\n",
    "Was passiert hier?\n",
    "\n",
    "### Die Funktion `promote(x,y,...)`\n",
    "\n",
    "Diese Funktion versucht, alle Argumente in einen gemeinsamen Typen umzuwandeln, der alle Werte (möglichst) exakt darstellen kann.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f1d4d57",
   "metadata": {},
   "outputs": [],
   "source": [
    "promote(12, 34.555, 77/99, 0xff)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27c2724e",
   "metadata": {},
   "outputs": [],
   "source": [
    "z = promote(BigInt(33), 27)\n",
    "@show z typeof(z);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8347acad",
   "metadata": {},
   "source": [
    "Die Funktion `promote()` verwendet dazu zwei Helfer, die Funktionen\n",
    " `promote_type(T1, T2)` und  `convert(T, x)`\n",
    "\n",
    "Wie üblich in Julia, kann man diesen Mechanismus durch [eigene *promotion rules* und `convert(T,x)`-Methoden erweitern.](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/) \n",
    "\n",
    "\n",
    "### Die Funktion `promote_type(T1, T2,...)` \n",
    "\n",
    "Sie ermittelt, zu welchem Typ umgewandelt werden soll. Argumente sind Typen, nicht Werte.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe551c95",
   "metadata": {},
   "outputs": [],
   "source": [
    "@show promote_type(Rational{Int64}, ComplexF64, Float32);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4c08732",
   "metadata": {},
   "source": [
    "### Die Funktion `convert(T,x)`\n",
    "\n",
    "Die Methoden von \n",
    "`convert(T, x)` wandeln `x` in ein Objekt vom Typ `T` um. Dabei sollte eine solche Umwandlung verlustfrei möglich sein.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d6aabe4",
   "metadata": {},
   "outputs": [],
   "source": [
    "z = convert(Float64, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b5205d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "z = convert(Int64, 23.00)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d4b80ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "z = convert(Int64, 2.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e3d953b",
   "metadata": {},
   "source": [
    "Die spezielle Rolle von `convert()` liegt darin, dass es an verschiedenen Stellen _implizit_ und automatisch eingesetzt wird: \n",
    "\n",
    "> [The following language constructs call convert](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#When-is-convert-called?):\n",
    "> \n",
    "  - Assigning to an array converts to the array's element type.\n",
    "  - Assigning to a field of an object converts to the declared type of the field.\n",
    "  - Constructing an object with new converts to the object's declared field types.\n",
    "  - Assigning to a variable with a declared type (e.g. local x::T) converts to that type.\n",
    "  - A function with a declared return type converts its return value to that type.\n",
    "\n",
    "-- und natürlich in `promote()`\n",
    "\n",
    "Für selbstdefinierte Datentypen kann man convert() um weitere Methoden ergänzen.\n",
    "\n",
    "Für Datentypen innerhalb der Number-Hierarchie gibt es wieder eine 'catch-all-Definition'\n",
    "```julia\n",
    "convert(::Type{T}, x::Number) where {T<:Number} = T(x)\n",
    "```\n",
    "\n",
    "Also: Wenn für einen Typen `T` aus der Hierarchie `T<:Number` ein Konstruktor `T(x)` mit einem numerischen Argument `x` existiert, dann wird dieser Konstruktor `T(x)` automatisch für Konvertierungen benutzt. (Natürlich können auch speziellere Methoden für `convert()` definiert werden, die dann Vorrang haben.)\n",
    "\n",
    "\n",
    "### Weitere Konstruktoren für `PComplex`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f9e1ebe",
   "metadata": {},
   "outputs": [],
   "source": [
    "## (a) r, ϕ beliebige Reals, z.B.  Integers, Rationals\n",
    "\n",
    "PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} = \n",
    "    PComplex{T}(convert(T, r), convert(T, ϕ))\n",
    "\n",
    "PComplex(r::T1, ϕ::T2) where {T1<:Real, T2<: Real} =  \n",
    "    PComplex{promote_type(Float64, T1, T2)}(r, ϕ)   \n",
    "\n",
    "## (b) Zur Umwandlung von Reals: Konstruktor mit \n",
    "##      nur einem Argument r\n",
    "\n",
    "PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} = \n",
    "    PComplex{T}(convert(T, r), convert(T, 0)) \n",
    "\n",
    "PComplex(r::S)  where {S<:Real} = \n",
    "    PComplex{promote_type(Float64, S)}(r, 0.0)\n",
    "\n",
    "## (c) Umwandlung Complex -> PComplex\n",
    "\n",
    "PComplex{T}(z::Complex{S}) where {T<:AbstractFloat, S<:Real} = \n",
    "    PComplex{T}(abs(z), angle(z))\n",
    "\n",
    "PComplex(z::Complex{S}) where {S<:Real} = \n",
    "    PComplex{promote_type(Float64, S)}(abs(z), angle(z))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c831e4c3",
   "metadata": {},
   "source": [
    "Ein Test der neuen Konstruktoren:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8103dc03",
   "metadata": {},
   "outputs": [],
   "source": [
    "3//5 ⋖ 45,  PComplex(Complex(1,1)),   PComplex(-13) "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c85b8d7a",
   "metadata": {},
   "source": [
    "Wir brauchen nun noch *promotion rules*, die festlegen, welcher Typ bei `promote(x::T1, y::T2)` herauskommen soll. Damit wird `promote_type()` intern um die nötigen weiteren Methoden erweitert. \n",
    "\n",
    "### *Promotion rules* für `PComplex`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e6a1e94",
   "metadata": {},
   "outputs": [],
   "source": [
    "Base.promote_rule(::Type{PComplex{T}}, ::Type{S}) where {T<:AbstractFloat,S<:Real} = \n",
    "    PComplex{promote_type(T,S)}\n",
    "\n",
    "Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where \n",
    "    {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ad30167",
   "metadata": {},
   "source": [
    "1. Regel:\n",
    ": Wenn ein `PComplex{T}` und ein `S<:Real` zusammentreffen, dann sollen beide zu `PComplex{U}` umgewandelt werden, wobei `U` der Typ ist, zu dem  `S` und `T` beide umgewandelt (_promoted_) werden können. \n",
    "\n",
    "2. Regel\n",
    ": Wenn ein `PComplex{T}` und ein `Complex{S}` zusammentreffen, dann sollen beide zu `PComplex{U}` umgewandelt werden, wobei `U` der Typ ist, zu dem  `S` und `T` beide umgewandelt werden können. \n",
    "\n",
    "\n",
    "\n",
    "Damit klappt nun die Multiplikation mit beliebigen numerischen Typen.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e72f683",
   "metadata": {},
   "outputs": [],
   "source": [
    "z3, 3z3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f0aec53e",
   "metadata": {},
   "outputs": [],
   "source": [
    "(3.0+2im) * (12⋖30.3),  12sqrt(z2) "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d881cc4",
   "metadata": {},
   "source": [
    ":::{.callout-caution icon=\"false\" collapse=\"true\" .titlenormal}\n",
    "\n",
    "## Zusammenfassung: unser Typ `PComplex`\n",
    "\n",
    "```julia\n",
    "struct PComplex{T <: AbstractFloat} <: Number\n",
    "    r :: T\n",
    "    ϕ :: T\n",
    "\n",
    "    function PComplex{T}(r::T, ϕ::T) where T<:AbstractFloat\n",
    "        if r<0            # flip the sign of r and correct phi\n",
    "            r = -r\n",
    "            ϕ += π\n",
    "        end\n",
    "        if r==0 ϕ=0 end  # normalize r=0 case to phi=0 \n",
    "        ϕ = mod(ϕ, 2π)   # map phi into interval [0,2pi)\n",
    "        new(r, ϕ)        # new() ist special function,\n",
    "    end                  #   available only inside inner constructors\n",
    "\n",
    "end\n",
    "\n",
    "# additional constructors\n",
    "PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)\n",
    "\n",
    "\n",
    "PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} = \n",
    "    PComplex{T}(convert(T, r), convert(T, ϕ))\n",
    "\n",
    "PComplex(r::T1, ϕ::T2) where {T1<:Real, T2<: Real} =  \n",
    "    PComplex{promote_type(Float64, T1, T2)}(r, ϕ)   \n",
    "\n",
    "\n",
    "PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} = \n",
    "    PComplex{T}(convert(T, r), convert(T, 0)) \n",
    "\n",
    "PComplex(r::S)  where {S<:Real} = \n",
    "    PComplex{promote_type(Float64, S)}(r, 0.0)\n",
    "\n",
    "\n",
    "PComplex{T}(z::Complex{S}) where {T<:AbstractFloat, S<:Real} = \n",
    "    PComplex{T}(abs(z), angle(z))\n",
    "\n",
    "PComplex(z::Complex{S}) where {S<:Real} = \n",
    "    PComplex{promote_type(Float64, S)}(abs(z), angle(z))\n",
    "\n",
    "# nice input\n",
    "⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)\n",
    "\n",
    "# nice output\n",
    "using Printf\n",
    "\n",
    "function Base.show(io::IO, z::PComplex)\n",
    "    # wir drucken die Phase in Grad, auf Zehntelgrad gerundet, \n",
    "    p = z.ϕ * 180/π\n",
    "    sp = @sprintf \"%.1f\" p\n",
    "    print(io, z.r, \"⋖\", sp, '°')\n",
    "end\n",
    "\n",
    "# arithmetic\n",
    "Base.sqrt(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)\n",
    "\n",
    "Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)\n",
    "\n",
    "# promotion rules\n",
    "Base.promote_rule(::Type{PComplex{T}}, ::Type{S}) where \n",
    "    {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}\n",
    "\n",
    "Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where \n",
    "    {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}\n",
    "```\n",
    ":::\n",
    "\n",
    ":::{.content-hidden unless-format=\"xxx\"}\n",
    "\n",
    "Jetzt geht sowas wie `PComplex(1, 0)` noch nicht. Wir wollen auch andere reelle Typen für `r` und `ϕ` zulassen. Der Einfachheit halber wandeln wir hier alles nach `Float64` um. Analog verfahren wir auch, wenn nur ein reelles oder komplexes Argument verwendet wird.\n",
    "\n",
    "```julia\n",
    "PComplex(r::Real, ϕ::Real) = PComplex(Float64(r), Float64(ϕ))\n",
    "PComplex(r::Real)          = PComplex(Float64(r), 0.0)\n",
    "PComplex(z::Complex)       = PComplex(abs(z), angle(z))\n",
    "\n",
    "z3 = PComplex(-2); z4 = PComplex(3im)\n",
    "@show z3 z4;\n",
    "```\n",
    "\n",
    ":::"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 1.8.5",
   "language": "julia",
   "name": "julia-1.8"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
