english improved
This commit is contained in:
@@ -9,10 +9,27 @@ engine: julia
|
||||
#| echo: false
|
||||
#| output: false
|
||||
using InteractiveUtils
|
||||
import QuartoNotebookWorker
|
||||
Base.stdout = QuartoNotebookWorker.with_context(stdout)
|
||||
myactive_module() = Main.Notebook
|
||||
Base.active_module() = myactive_module()
|
||||
|
||||
function Base.show(io::IO, x::T) where T
|
||||
if parentmodule(T) == @__MODULE__
|
||||
# Print "TypeName(fields...)" without module prefix
|
||||
print(io, nameof(T), "(")
|
||||
fields = fieldnames(T)
|
||||
for (i, f) in enumerate(fields)
|
||||
print(io, getfield(x, f))
|
||||
i < length(fields) && print(io, ", ")
|
||||
end
|
||||
print(io, ")")
|
||||
else
|
||||
invoke(Base.show, Tuple{IO, Any}, io, x)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##import QuartoNotebookWorker
|
||||
##Base.stdout = QuartoNotebookWorker.with_context(stdout)
|
||||
##myactive_module() = Main.Notebook
|
||||
##Base.active_module() = myactive_module()
|
||||
# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L516-L520
|
||||
# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L3073-L3077
|
||||
```
|
||||
@@ -28,25 +45,23 @@ We want to introduce a new numeric type **complex numbers in polar representatio
|
||||
A first attempt could look like this:
|
||||
|
||||
```{julia}
|
||||
struct PComplex1{T <: AbstractFloat} <: Number
|
||||
struct PComplex{T <: AbstractFloat} <: Number
|
||||
r :: T
|
||||
ϕ :: T
|
||||
end
|
||||
|
||||
z1 = PComplex1(-32.0, 33.0)
|
||||
z2 = PComplex1{Float32}(12, 13)
|
||||
#const PComplex = Main.Notebook.PComplex #| hide_line
|
||||
#Base.show(io::IO, ::Type{PComplex}) = print(io, "PComplex") #| hide_line
|
||||
z1 = PComplex(-32.0, 33.0)
|
||||
z2 = PComplex{Float32}(12, 13)
|
||||
@show z1 z2;
|
||||
```
|
||||
|
||||
:::{.callout-warning collapse="true" .titlenormal}
|
||||
##
|
||||
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 automatically provides *default constructors*:
|
||||
|
||||
- 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.
|
||||
- The constructor `PComplex` infers type `T` from the arguments, and
|
||||
- Constructors like `PComplex{Float64}` accept explicit type specifications. Arguments are converted to the requested type.
|
||||
|
||||
------
|
||||
|
||||
@@ -93,9 +108,9 @@ end
|
||||
```{julia}
|
||||
z1 = PComplex{Float64}(-3.3, 7π+1)
|
||||
```
|
||||
For explicitly specifying an *inner constructor*, we pay a price: Julia's *default constructors* are no longer available.
|
||||
However, explicitly specifying an *inner constructor* has a consequence: Julia's *default constructors* are no longer available.
|
||||
|
||||
The constructor without explicit type specification in curly braces, which takes over the type of the arguments, is also desired:
|
||||
The constructor without explicit type specification, which infers the type from the arguments, is also needed:
|
||||
|
||||
```{julia}
|
||||
PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)
|
||||
@@ -134,14 +149,14 @@ Details will be discussed in a later chapter.
|
||||
|
||||
:::
|
||||
|
||||
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>`.
|
||||
The angle bracket symbol `∠` is not available as a Julia operator. We use `⋖` as an alternative, entered as `\lessdot<Tab>`.
|
||||
|
||||
```{julia}
|
||||
⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)
|
||||
|
||||
z3 = 2. ⋖ 90.
|
||||
```
|
||||
(The type annotation -- `Real` instead of `AbstractFloat` -- is a preview of further constructors to come. For now, the operator `⋖` only works with `Float`s.)
|
||||
(The type annotation `Real` instead of `AbstractFloat` anticipates further constructors. Currently, the operator `⋖` works only with `Float64`.)
|
||||
|
||||
|
||||
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).
|
||||
@@ -150,7 +165,7 @@ Of course, we also want the output to look nice. Details can be found in the [do
|
||||
using Printf
|
||||
|
||||
function Base.show(io::IO, z::PComplex)
|
||||
# we print the phase in degrees, rounded to tenths of a degree,
|
||||
# print phase in degrees, rounded to one decimal place
|
||||
p = z.ϕ * 180/π
|
||||
sp = @sprintf "%.1f" p
|
||||
print(io, z.r, "⋖", sp, '°')
|
||||
@@ -162,20 +177,20 @@ end
|
||||
|
||||
## Methods for `PComplex`
|
||||
|
||||
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.
|
||||
For our type to be a proper member of the family of types derived from `Number`, additional functionality is required: arithmetic operations, comparison operators, and conversions must all be defined.
|
||||
|
||||
|
||||
We limit ourselves to multiplication and square roots.
|
||||
We focus on multiplication and square root operations.
|
||||
|
||||
|
||||
:::{.callout-note collapse="true"}
|
||||
:::{.callout-note collapse="false"}
|
||||
## Modules
|
||||
|
||||
- To add to the `methods` of existing functions and operations, one must address them with their 'full name'.
|
||||
- Adding methods to existing functions requires using their fully qualified names.
|
||||
- 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.
|
||||
- Most basic functions belong to `Base`, which is loaded automatically.
|
||||
- Without user-defined modules, definitions reside in `Main`.
|
||||
- The macro `@which` applied to a name shows its defining module.
|
||||
|
||||
```{julia}
|
||||
f(x) = 3x^3
|
||||
@@ -191,7 +206,7 @@ println("Module for addition: $wp, Module for sqrt: $ws")
|
||||
:::
|
||||
|
||||
```{julia}
|
||||
qwurzel(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)
|
||||
sqrt_polar(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)
|
||||
```
|
||||
|
||||
```{julia}
|
||||
@@ -212,9 +227,9 @@ The function `sqrt()` already has some methods:
|
||||
length(methods(sqrt))
|
||||
```
|
||||
|
||||
Now it will have one more method:
|
||||
Adding one more method:
|
||||
```{julia}
|
||||
Base.sqrt(z::PComplex) = qwurzel(z)
|
||||
Base.sqrt(z::PComplex) = sqrt_polar(z)
|
||||
|
||||
length(methods(sqrt))
|
||||
```
|
||||
@@ -223,26 +238,26 @@ length(methods(sqrt))
|
||||
sqrt(z2)
|
||||
```
|
||||
|
||||
and now for multiplication:
|
||||
For multiplication:
|
||||
|
||||
```{julia}
|
||||
Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)
|
||||
|
||||
@show z1 * z2;
|
||||
```
|
||||
(Since the operator symbol is not a normal name, the colon must be with `Base.` in the composition.)
|
||||
(Since `:` is not a valid identifier character, it must be qualified with `Base.`)
|
||||
|
||||
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.
|
||||
However, multiplication with other numeric types is not yet supported. Many corresponding methods could be defined, but Julia provides another mechanism for *numeric types* that simplifies this:
|
||||
|
||||
|
||||
## Type Promotion and Conversion
|
||||
|
||||
In Julia, one can naturally use the most diverse numeric types side by side.
|
||||
Julia supports freely mixing various numeric types:
|
||||
|
||||
```{julia}
|
||||
1//3 + 5 + 5.2 + 0xff
|
||||
```
|
||||
If one looks at the numerous methods defined, for example, for `+` and `*`, one finds among them a kind of 'catch-all definition'
|
||||
Among the numerous methods defined for `+` and `*`, we find a catch-all definition:
|
||||
|
||||
```julia
|
||||
+(x::Number, y::Number) = +(promote(x,y)...)
|
||||
@@ -251,7 +266,7 @@ If one looks at the numerous methods defined, for example, for `+` and `*`, one
|
||||
|
||||
|
||||
|
||||
(The 3 dots are the splat operator, which decomposes the tuple returned by promote() back into its components.)
|
||||
(The three dots form the splat operator, which decomposes the tuple returned by `promote()` into its components.)
|
||||
|
||||
Since the method with the types `(Number, Number)` is very general, it is only used when more specific methods do not apply.
|
||||
|
||||
@@ -273,12 +288,12 @@ z = promote(BigInt(33), 27)
|
||||
The function `promote()` uses two helpers, the functions
|
||||
`promote_type(T1, T2)` and `convert(T, x)`
|
||||
|
||||
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/)
|
||||
As usual in Julia, we can extend this mechanism with our own custom [*promotion rules* and `convert(T,x)` methods.](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/)
|
||||
|
||||
|
||||
### The Function `promote_type(T1, T2,...)`
|
||||
|
||||
It determines to which type conversion should take place. Arguments are types, not values.
|
||||
It determines to which type the conversion should take place. Arguments are types, not values.
|
||||
|
||||
```{julia}
|
||||
@show promote_type(Rational{Int64}, ComplexF64, Float32);
|
||||
@@ -303,7 +318,7 @@ z = convert(Int64, 23.00)
|
||||
z = convert(Int64, 2.3)
|
||||
```
|
||||
|
||||
The special role of `convert()` lies in the fact that it is used *implicitly* and automatically at various points:
|
||||
The special role of `convert()` is that it is called implicitly at various points:
|
||||
|
||||
> [The following language constructs call convert](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#When-is-convert-called?):
|
||||
>
|
||||
@@ -315,14 +330,14 @@ The special role of `convert()` lies in the fact that it is used *implicitly* an
|
||||
|
||||
-- and of course in `promote()`
|
||||
|
||||
For self-defined data types, one can extend convert() with further methods.
|
||||
For user-defined types, `convert()` can be extended with custom methods.
|
||||
|
||||
For data types within the Number hierarchy, there is again a 'catch-all definition'
|
||||
Within the `Number` hierarchy, a generic method handles conversions:
|
||||
```julia
|
||||
convert(::Type{T}, x::Number) where {T<:Number} = T(x)
|
||||
```
|
||||
|
||||
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.)
|
||||
Therefore: If a type `T<:Number` has a constructor `T(x)` accepting a numeric argument, this constructor is automatically used for conversions. (More specific methods for `convert()` can also be defined and will take priority.)
|
||||
|
||||
|
||||
### Further Constructors for `PComplex`
|
||||
@@ -330,7 +345,7 @@ So: If for a type `T` from the hierarchy `T<:Number` there exists a constructor
|
||||
|
||||
```{julia}
|
||||
|
||||
## (a) r, ϕ arbitrary reals, e.g. Integers, Rationals
|
||||
## (a) Arbitrary real types for r and ϕ (e.g., integers, rationals)
|
||||
|
||||
PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} =
|
||||
PComplex{T}(convert(T, r), convert(T, ϕ))
|
||||
@@ -358,7 +373,7 @@ PComplex(z::Complex{S}) where {S<:Real} =
|
||||
|
||||
```
|
||||
|
||||
A test of the new constructors:
|
||||
Testing the new constructors:
|
||||
|
||||
```{julia}
|
||||
|
||||
@@ -367,7 +382,7 @@ A test of the new constructors:
|
||||
```
|
||||
|
||||
|
||||
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* are needed to determine the result type of `promote(x::T1, y::T2)`. This mechanism extends `promote_type()` with the necessary methods.
|
||||
|
||||
### *Promotion rules* for `PComplex`
|
||||
|
||||
@@ -379,16 +394,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. 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_).
|
||||
1. **Rule:**
|
||||
When a `PComplex{T}` and an `S<:Real` are combined, both convert to `PComplex{U}`, where `U` is the promoted type of `S` and `T`.
|
||||
|
||||
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.
|
||||
2. **Rule**
|
||||
When a `PComplex{T}` and a `Complex{S}` are combined, both convert to `PComplex{U}`, where `U` is the promoted type of `S` and `T`.
|
||||
|
||||
|
||||
|
||||
|
||||
Now multiplication with arbitrary numeric types works.
|
||||
We can now multiply with arbitrary numeric types:
|
||||
|
||||
```{julia}
|
||||
z3, 3z3
|
||||
@@ -453,7 +468,7 @@ PComplex(z::Complex{S}) where {S<:Real} =
|
||||
using Printf
|
||||
|
||||
function Base.show(io::IO, z::PComplex)
|
||||
# we print the phase in degrees, rounded to tenths of a degree,
|
||||
# print phase in degrees, rounded to one decimal place
|
||||
p = z.ϕ * 180/π
|
||||
sp = @sprintf "%.1f" p
|
||||
print(io, z.r, "⋖", sp, '°')
|
||||
@@ -475,7 +490,7 @@ Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where
|
||||
|
||||
:::{.content-hidden unless-format="xxx"}
|
||||
|
||||
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.
|
||||
`PComplex(1, 0)` is not yet supported. Other real types for `r` and `ϕ` should also be supported. For simplicity, all types are converted to `Float64`. Analogous handling applies for single real or complex arguments.
|
||||
|
||||
```julia
|
||||
PComplex(r::Real, ϕ::Real) = PComplex(Float64(r), Float64(ϕ))
|
||||
|
||||
Reference in New Issue
Block a user