english translation started

This commit is contained in:
2026-02-22 18:22:46 +01:00
parent 19c67a3c9b
commit 9e418381ca
17 changed files with 1915 additions and 1907 deletions

View File

@@ -2,7 +2,7 @@
engine: julia
---
# Ein Fallbeispiel: Der parametrisierte Datentyp PComplex
# A Case Study: The Parametric Data Type PComplex
```{julia}
#| error: false
@@ -18,14 +18,14 @@ Base.active_module() = myactive_module()
```
Wir wollen als neuen numerischen Typen **komplexe Zahlen in Polardarstellung $z=r e^{i\phi}=(r,ϕ)$** einführen.
We want to introduce a new numeric type **complex numbers in polar representation $z=r e^{i\phi}=(r,\phi)$**.
- Der Typ soll sich in die Typhierarchie einfügen als Subtyp von 'Number'.
- $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.)
- The type should integrate into the type hierarchy as a subtype of 'Number'.
- $r$ and $\phi$ should be floating point numbers. (Unlike complex numbers in 'Cartesian' coordinates, restricting to integer values of r or $\phi$ makes little mathematical sense.)
## Die Definition von `PComplex`
## The Definition of `PComplex`
Ein erster Versuch könnte so aussehen:
A first attempt could look like this:
```{julia}
struct PComplex1{T <: AbstractFloat} <: Number
@@ -40,25 +40,25 @@ z2 = PComplex1{Float32}(12, 13)
:::{.callout-warning collapse="true" .titlenormal}
##
Es ist nicht möglich, in einer Julia-Session eine einmal definierte `struct` später umzudefinieren. Daher verwende ich verschiedene Namen. Eine andere Möglichkeit ist z.B. die Verwendung von [`ProtoStructs.jl`](https://juliahub.com/ui/Packages/General/ProtoStructs).
It is not possible to redefine a `struct` once it has been defined in a Julia session. Therefore, I use different names. Another possibility is, for example, the use of [`ProtoStructs.jl`](https://juliahub.com/ui/Packages/General/ProtoStructs).
:::
Julia stellt automatisch *default constructors* zur Verfügung:
Julia automatically provides *default constructors*:
- den Konstruktor `PComplex1`, bei dem der Typ `T` von den übergebenen Argumenten abgeleitet wird und
- Konstruktoren `PComplex{Float64},...` mit expliziter Typangabe. Hier wird versucht, die Argumente in den angeforderten Typ zu konvertieren.
- the constructor `PComplex1`, where the type `T` is inferred from the passed arguments, and
- constructors `PComplex{Float64},...` with explicit type specification. Here, the arguments are attempted to be converted to the requested type.
------
Wir wollen nun, dass der Konstruktor noch mehr tut.
In der Polardarstellung soll $0\le r$ und $0\le \phi<2\pi$ gelten.
We now want the constructor to do even more.
In the polar representation, we want $0\le r$ and $0\le \phi<2\pi$ to hold.
Wenn die übergebenen Argumente das nicht erfüllen, sollten sie entsprechend umgerechnet werden.
If the passed arguments do not satisfy this, they should be recalculated accordingly.
Dazu definieren wir einen _inner constructor_, der den _default constructor_ ersetzt.
To this end, we define an _inner constructor_ that replaces the _default constructor_.
- Ein _inner constructor_ ist eine Funktion innerhalb der `struct`-Definition.
- In einem _inner constructor_ kann man die spezielle Funktion `new` verwenden, die wie der _default constructor_ wirkt.
- An _inner constructor_ is a function within the `struct` definition.
- In an _inner constructor_, one can use the special function `new`, which acts like the _default constructor_.
```{julia}
@@ -73,18 +73,17 @@ struct PComplex{T <: AbstractFloat} <: Number
end
if r==0 ϕ=0 end # normalize r=0 case to phi=0
ϕ = mod(ϕ, 2π) # map phi into interval [0,2pi)
new(r, ϕ) # new() ist special function,
new(r, ϕ) # new() is special function,
end # available only inside inner constructors
end
```
```{julia}
#| echo: false
#| output: false
#=
in den ganzen quarto-runs wollen wir hier noch das default-show benutzen
in the whole quarto-runs we want to use the default show here
=#
zz = @which Base.show(stdout, PComplex{Float64}(2.,3.))
if zz.module != Base
@@ -94,9 +93,9 @@ end
```{julia}
z1 = PComplex{Float64}(-3.3, 7π+1)
```
Für die explizite Angabe eines *inner constructors* müssen wir allerdings einen Preis zahlen: Die sonst von Julia bereitgestellten *default constructors* fehlen.
For the explicit specification of an *inner constructor*, we have to pay a price: The *default constructors* provided by Julia are missing.
Den Konstruktor, der ohne explizite Typangabe in geschweiften Klammern auskommt und den Typ der Argumente übernimmt, wollen wir gerne auch haben:
The constructor without explicit type specification in curly braces, which takes over the type of the arguments, is also desired:
```{julia}
PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)
@@ -105,43 +104,43 @@ z2 = PComplex(2.0, 0.3)
```
## Eine neue Schreibweise
## A New Notation
Julia verwendet `//` als Infix-Konstruktor für den Typ `Rational`. Sowas Schickes wollen wir auch.
Julia uses `//` as an infix constructor for the type `Rational`. We want something equally nice.
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:
In electronics/electrical engineering, [AC quantities are described by complex numbers.](https://en.wikipedia.org/wiki/Phasor_analysis) A representation of complex numbers by "magnitude" and "phase" is common and is often represented in so-called [phasor form](https://en.wikipedia.org/wiki/Phasor):
$$
z= r\enclose{phasorangle}{\phi} = 3.4\;\enclose{phasorangle}{45^\circ}
z= r\enclose{phasorangle}{\phi} = 3.4\;\enclose{phasorangle}{45^\circ}
$$
wobei man in der Regel den Winkel in Grad notiert.
where the angle is usually noted in degrees.
:::{.callout-note .titlenormal collapse="true"}
## Mögliche Infix-Operatoren in Julia
## Possible Infix Operators in Julia
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)
In Julia, a large number of Unicode characters are reserved for use as operators. The definitive list is in the [parser source code.](https://github.com/JuliaLang/julia/blob/eaa2c58aeb12f27c1d8c116ab111773a4fc4495f/src/julia-parser.scm#L13-L31)
Auf Details werden wir in einem späteren Kapitel noch eingehen.
Details will be discussed in a later chapter.
:::
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.
The angle bracket symbol `∠` is not available as an operator symbol. We use `⋖` as an alternative. This can be entered in Julia as `\lessdot<tab>`.
```{julia}
⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)
z3 = 2. ⋖ 90.
```
(Die Typ-Annotation -- `Real` statt `AbstractFloat` -- ist ein Vorgriff auf kommende weitere Konstruktoren. Im Moment funktioniert der Operator `⋖` erstmal nur mit `Float`s.)
(The type annotation -- `Real` instead of `AbstractFloat` -- is a preview of further constructors to come. For now, the operator `⋖` only works with `Float`s.)
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).
Of course, we also want the output to look nice. Details can be found in the [documentation](https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing).
```{julia}
using Printf
function Base.show(io::IO, z::PComplex)
# wir drucken die Phase in Grad, auf Zehntelgrad gerundet,
# we print the phase in degrees, rounded to tenths of a degree,
p = z.ϕ * 180/π
sp = @sprintf "%.1f" p
print(io, z.r, "⋖", sp, '°')
@@ -151,22 +150,22 @@ end
```
## Methoden für `PComplex`
## Methods for `PComplex`
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.
For our type to be a proper member of the family of types derived from `Number`, we need a whole lot more. Arithmetic, comparison operators, conversions, etc. must be defined.
Wir beschränken uns auf Multiplikation und Quadratwurzeln.
We limit ourselves to multiplication and square roots.
:::{.callout-note collapse="true"}
## Module
## Modules
- Um die `methods` der existierenden Funktionen und Operationen zu ergänzen, muss man diese mit ihrem 'vollen Namen' ansprechen.
- Alle Objekte gehören zu einem Namensraum oder `module`.
- Die meisten Basisfunktionen gehören zum Modul `Base`, welches standardmäßig immer ohne explizites `using ...` geladen wird.
- Solange man keine eigenen Module definiert, sind die eigenen Definitionen im Modul `Main`.
- Das Macro `@which`, angewendet auf einen Namen, zeigt an, in welchem Modul der Name definiert wurde.
- To add to the `methods` of existing functions and operations, one must address them with their 'full name'.
- All objects belong to a namespace or `module`.
- Most basic functions belong to the module `Base`, which is always loaded without explicit `using ...` by default.
- As long as one does not define own modules, own definitions are in the module `Main`.
- The macro `@which`, applied to a name, shows in which module the name is defined.
```{julia}
f(x) = 3x^3
@@ -176,7 +175,7 @@ f(x) = 3x^3
```{julia}
wp = @which +
ws = @which(sqrt)
println("Modul für Addition: $wp, Modul für sqrt: $ws")
println("Module for addition: $wp, Module for sqrt: $ws")
```
:::
@@ -190,7 +189,7 @@ qwurzel(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)
#| output: false
#=
damit das length(methods(sqrt)) klappt
to make length(methods(sqrt)) work
=#
if hasmethod(sqrt, (PComplex,))
zz = @which Base.sqrt(PComplex{Float64}(1.,1.))
@@ -198,12 +197,12 @@ if hasmethod(sqrt, (PComplex,))
end
```
Die Funktion `sqrt()` hat schon einige Methoden:
The function `sqrt()` already has some methods:
```{julia}
length(methods(sqrt))
```
Jetzt wird es eine Methode mehr:
Now it will have one more method:
```{julia}
Base.sqrt(z::PComplex) = qwurzel(z)
@@ -214,26 +213,26 @@ length(methods(sqrt))
sqrt(z2)
```
und nun zur Multiplikation:
and now for multiplication:
```{julia}
Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)
@show z1 * z2;
```
(Da das Operatorsymbol kein normaler Name ist, muss der Doppelpunkt bei der Zusammensetzung mit `Base.` sein.)
(Since the operator symbol is not a normal name, the colon must be with `Base.` in the composition.)
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.
We can, however, not yet multiply with other numeric types. One could now define a large number of corresponding methods. Julia provides one more mechanism for *numeric types* that simplifies this somewhat.
## Typ-Promotion und Konversion
## Type Promotion and Conversion
In Julia kann man bekanntlich die verschiedensten numerischen Typen nebeneinander verwenden.
In Julia, one can naturally use the most diverse numeric types side by side.
```{julia}
1//3 + 5 + 5.2 + 0xff
```
Wenn man in die zahlreichen Methoden schaut, die z.B. für `+` und `*` definiert sind, findet man u.a. eine Art 'catch-all-Definition'
If one looks at the numerous methods defined, for example, for `+` and `*`, one finds among them a kind of 'catch-all definition'
```julia
+(x::Number, y::Number) = +(promote(x,y)...)
@@ -242,15 +241,15 @@ Wenn man in die zahlreichen Methoden schaut, die z.B. für `+` und `*` definiert
(Die 3 Punkte sind der splat-Operator, der das von promote() zurückgegebene Tupel wieder in seine Bestandteile zerlegt.)
(The 3 dots are the splat operator, which decomposes the tuple returned by promote() back into its components.)
Da die Methode mit den Typen `(Number, Number)` sehr allgemein ist, wird sie erst verwendet, wenn spezifischere Methoden nicht greifen.
Since the method with the types `(Number, Number)` is very general, it is only used when more specific methods do not apply.
Was passiert hier?
What happens here?
### Die Funktion `promote(x,y,...)`
### The Function `promote(x,y,...)`
Diese Funktion versucht, alle Argumente in einen gemeinsamen Typen umzuwandeln, der alle Werte (möglichst) exakt darstellen kann.
This function attempts to convert all arguments to a common type that can represent all values (as precisely as possible).
```{julia}
promote(12, 34.555, 77/99, 0xff)
@@ -261,15 +260,15 @@ z = promote(BigInt(33), 27)
@show z typeof(z);
```
Die Funktion `promote()` verwendet dazu zwei Helfer, die Funktionen
`promote_type(T1, T2)` und `convert(T, x)`
The function `promote()` uses two helpers, the functions
`promote_type(T1, T2)` and `convert(T, x)`
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/)
As usual in Julia, one can extend this mechanism with [custom *promotion rules* and `convert(T,x)` methods.](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/)
### Die Funktion `promote_type(T1, T2,...)`
### The Function `promote_type(T1, T2,...)`
Sie ermittelt, zu welchem Typ umgewandelt werden soll. Argumente sind Typen, nicht Werte.
It determines to which type conversion should take place. Arguments are types, not values.
```{julia}
@show promote_type(Rational{Int64}, ComplexF64, Float32);
@@ -277,10 +276,10 @@ Sie ermittelt, zu welchem Typ umgewandelt werden soll. Argumente sind Typen, nic
### Die Funktion `convert(T,x)`
### The Function `convert(T,x)`
Die Methoden von
`convert(T, x)` wandeln `x` in ein Objekt vom Typ `T` um. Dabei sollte eine solche Umwandlung verlustfrei möglich sein.
The methods of
`convert(T, x)` convert `x` into an object of type `T`. Such a conversion should be lossless.
```{julia}
z = convert(Float64, 3)
@@ -294,7 +293,7 @@ z = convert(Int64, 23.00)
z = convert(Int64, 2.3)
```
Die spezielle Rolle von `convert()` liegt darin, dass es an verschiedenen Stellen _implizit_ und automatisch eingesetzt wird:
The special role of `convert()` lies in the fact that it is used *implicitly* and automatically at various points:
> [The following language constructs call convert](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#When-is-convert-called?):
>
@@ -304,24 +303,24 @@ Die spezielle Rolle von `convert()` liegt darin, dass es an verschiedenen Stelle
- Assigning to a variable with a declared type (e.g. local x::T) converts to that type.
- A function with a declared return type converts its return value to that type.
-- und natürlich in `promote()`
-- and of course in `promote()`
Für selbstdefinierte Datentypen kann man convert() um weitere Methoden ergänzen.
For self-defined data types, one can extend convert() with further methods.
Für Datentypen innerhalb der Number-Hierarchie gibt es wieder eine 'catch-all-Definition'
For data types within the Number hierarchy, there is again a 'catch-all definition'
```julia
convert(::Type{T}, x::Number) where {T<:Number} = T(x)
```
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.)
So: If for a type `T` from the hierarchy `T<:Number` there exists a constructor `T(x)` with a numeric argument `x`, then this constructor `T(x)` is automatically used for conversions. (Of course, more specific methods for `convert()` can also be defined, which then have priority.)
### Weitere Konstruktoren für `PComplex`
### Further Constructors for `PComplex`
```{julia}
## (a) r, ϕ beliebige Reals, z.B. Integers, Rationals
## (a) r, ϕ arbitrary reals, e.g. Integers, Rationals
PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} =
PComplex{T}(convert(T, r), convert(T, ϕ))
@@ -329,8 +328,8 @@ PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} =
PComplex(r::T1, ϕ::T2) where {T1<:Real, T2<: Real} =
PComplex{promote_type(Float64, T1, T2)}(r, ϕ)
## (b) Zur Umwandlung von Reals: Konstruktor mit
## nur einem Argument r
## (b) For conversion from reals: constructor with
## only one argument r
PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} =
PComplex{T}(convert(T, r), convert(T, 0))
@@ -338,7 +337,7 @@ PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} =
PComplex(r::S) where {S<:Real} =
PComplex{promote_type(Float64, S)}(r, 0.0)
## (c) Umwandlung Complex -> PComplex
## (c) Conversion Complex -> PComplex
PComplex{T}(z::Complex{S}) where {T<:AbstractFloat, S<:Real} =
PComplex{T}(abs(z), angle(z))
@@ -349,8 +348,7 @@ PComplex(z::Complex{S}) where {S<:Real} =
```
Ein Test der neuen Konstruktoren:
A test of the new constructors:
```{julia}
@@ -359,9 +357,9 @@ Ein Test der neuen Konstruktoren:
```
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.
We now still need *promotion rules* that determine which type should result from `promote(x::T1, y::T2)`. This internally extends `promote_type()` with the necessary further methods.
### *Promotion rules* für `PComplex`
### *Promotion rules* for `PComplex`
```{julia}
@@ -371,15 +369,16 @@ Base.promote_rule(::Type{PComplex{T}}, ::Type{S}) where {T<:AbstractFloat,S<:Rea
Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where
{T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}
```
1. Regel:
: 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.
1. Rule:
: If a `PComplex{T}` and an `S<:Real` meet, then both should be converted to `PComplex{U}`, where `U` is the type to which `S` and `T` can both be converted (_promoted_).
2. Regel
: 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.
2. Rule
: If a `PComplex{T}` and a `Complex{S}` meet, then both should be converted to `PComplex{U}`, where `U` is the type to which `S` and `T` can be converted.
Damit klappt nun die Multiplikation mit beliebigen numerischen Typen.
Now multiplication with arbitrary numeric types works.
```{julia}
z3, 3z3
@@ -391,9 +390,10 @@ z3, 3z3
:::{.callout-caution icon="false" collapse="true" .titlenormal}
## Zusammenfassung: unser Typ `PComplex`
## Summary: our type `PComplex`
```julia
struct PComplex{T <: AbstractFloat} <: Number
@@ -407,7 +407,7 @@ struct PComplex{T <: AbstractFloat} <: Number
end
if r==0 ϕ=0 end # normalize r=0 case to phi=0
ϕ = mod(ϕ, 2π) # map phi into interval [0,2pi)
new(r, ϕ) # new() ist special function,
new(r, ϕ) # new() is special function,
end # available only inside inner constructors
end
@@ -443,7 +443,7 @@ PComplex(z::Complex{S}) where {S<:Real} =
using Printf
function Base.show(io::IO, z::PComplex)
# wir drucken die Phase in Grad, auf Zehntelgrad gerundet,
# we print the phase in degrees, rounded to tenths of a degree,
p = z.ϕ * 180/π
sp = @sprintf "%.1f" p
print(io, z.r, "⋖", sp, '°')
@@ -465,7 +465,7 @@ Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where
:::{.content-hidden unless-format="xxx"}
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.
Now something like `PComplex(1, 0)` does not work yet. We also want to allow other real types for `r` and `ϕ`. For simplicity, we convert everything to `Float64` here. We proceed analogously if only one real or complex argument is used.
```julia
PComplex(r::Real, ϕ::Real) = PComplex(Float64(r), Float64(ϕ))