Skip to content

Commit fbefbda

Browse files
committed
Finished lecture_05 (basic structure)
1 parent 18bbc1a commit fbefbda

File tree

2 files changed

+128
-108
lines changed

2 files changed

+128
-108
lines changed

docs/src/lecture_05/compositetypes.md

Lines changed: 118 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -230,28 +230,9 @@ true
230230
</p></div>
231231
```
232232

233-
```@raw html
234-
<div class = "exercise-body">
235-
<header class = "exercise-header">Exercise:</header><p>
236-
```
237-
238-
Some text that describes the exercise
239-
240-
```@raw html
241-
</p></div>
242-
<details class = "solution-body">
243-
<summary class = "solution-header">Solution:</summary><p>
244-
```
245-
246-
Solution
247-
248-
```@raw html
249-
</p></details>
250-
```
251-
252233
## Parametric types
253234

254-
An important and powerful feature of Julia's type system is that it is parametric: types can take parameters. It means that type declaration actually introduces a whole family of new types (one for each possible combination of parameter values). Parametric (abstract) types can be defined as follows.
235+
An important and powerful feature of Julia's type system is that it is parametric: types can take parameters. It means that type declaration actually introduces a whole family of new types (one for each possible combination of parameter values). Parametric abstract and composite types can be defined as follows.
255236

256237
```jldoctest structs; output = false
257238
abstract type AbstractPoint{T} end
@@ -278,20 +259,14 @@ true
278259
Thus, this single declaration actually declares concrete type for each type `T` that is a subtype of `Real`. The `Point` type itself is also a valid type object, containing all instances `Point{Float64}`, `Point{Int64}`, etc., as subtypes.
279260

280261
```jldoctest structs
281-
julia> Point{Float64} <: Point
282-
true
283-
284-
julia> Point{Int64} <: Point
285-
true
286-
287-
julia> Point{Float64} <: AbstractPoint
262+
julia> Point{Float64} <: Point <: AbstractPoint
288263
true
289264
290-
julia> Point{Int64} <: AbstractPoint
265+
julia> Point{Int64} <: Point <: AbstractPoint
291266
true
292267
```
293268

294-
However, concrete `Point` types with different `T` values are never subtypes of each other. Even though `Float64` is a subtype of ` Real`, the `Point{Float64}` is not a subtype of `Point{Real}`.
269+
However, concrete `Point{T}` types with different `T` values are never subtypes of each other. Even though `Float64` is a subtype of ` Real`, the `Point{Float64}` is not a subtype of `Point{Real}`.
295270

296271
```jldoctest structs
297272
julia> Point{Float64} <: Point{Real}
@@ -306,47 +281,59 @@ false
306281

307282
This behavior is for practical reasons: while any instance of `Point{Float64}` may conceptually be like an instance of `Point{Real}` as well, the two types have different representations in memory:
308283

309-
- An instance of `Point{Float64}` can be represented compactly and efficiently as an immediate pair of 64-bit values;
284+
- An instance of `Point{Float64}` can be represented compactly and efficiently as an immediate pair of 64-bit values.
310285
- An instance of `Point{Real}` must be able to hold any pair of instances of `Real`. Since objects that are instances of `Real` can be of arbitrary size and structure, in practice, an instance of `Point{Real}` must be represented as a pair of pointers to individually allocated `Real` objects.
311286

312287
The efficiency gained by being able to store `Point{Float64}` objects with immediate values is magnified enormously in the case of arrays: an `Array{Float64}` can be stored as a contiguous memory block of 64-bit floating-point values, whereas an `Array{Real}` must be an array of pointers to individually allocated `Real` objects - which may well be boxed 64-bit floating-point values, but also might be arbitrarily large, complex objects, which are declared to be implementations of the `Real` abstract type.
313288

314289
Since `Point{Float64}` is not a subtype of `Point{Real}`, the following method can't be applied to arguments of type `Point{Float64}`.
315290

316-
```jldoctest structs
317-
julia> norm(p::Point{Real}) = sqrt(p.x^2 + p.y^2)
318-
norm (generic function with 1 method)
291+
```julia structs
292+
julia> coordinates(p::Point{Real}) = (p.x, p.y)
319293

320-
julia> norm(Point(1,2))
321-
ERROR: MethodError: no method matching norm(::Point{Int64})
294+
julia> coordinates(Point(1,2))
295+
ERROR: MethodError: no method matching coordinates(::Point{Int64})
322296
[...]
323297

324-
julia> norm(Point(1.0,2.0))
325-
ERROR: MethodError: no method matching norm(::Point{Float64})
298+
julia> coordinates(Point(1.0,2.0))
299+
ERROR: MethodError: no method matching coordinates(::Point{Float64})
326300
[...]
327301
```
328302

303+
329304
A correct way to define a method that accepts all arguments of type `Point{T}` where `T` is a subtype of `Real` is as follows.
330305

331306
```jldoctest structs
332-
julia> norm(p::Point{<:Real}) = sqrt(p.x^2 + p.y^2)
333-
norm (generic function with 2 methods)
307+
julia> coordinates(p::Point{<:Real}) = (p.x, p.y)
308+
coordinates (generic function with 1 method)
334309
335-
julia> norm(Point(1,2))
336-
2.23606797749979
310+
julia> coordinates(Point(1,2))
311+
(1, 2)
337312
338-
julia> norm(Point(1.0,2.0))
339-
2.23606797749979
313+
julia> coordinates(Point(1.0,2.0))
314+
(1.0, 2.0)
315+
```
316+
317+
Or simply use the `Point` type without specified parameter. It is also possible to define function for all subtypes of some abstract type.
318+
319+
```jldoctest structs
320+
julia> Base.show(io::IO, p::AbstractPoint) = print(io, coordinates(p))
321+
322+
julia> Point(4, 2)
323+
(4, 2)
324+
325+
julia> Point(0.2, 1.3)
326+
(0.2, 1.3)
340327
```
341328

342329
There are two ways how to instantiate the `Point` type. The first way is to create an instance of `Point{T}` without specifying the `T` parameter and letting Julia decide which type should be used. The second way is to specify the `T` parameter manually.
343330

344331
```jldoctest structs
345332
julia> Point(1, 2)
346-
Point{Int64}(1, 2)
333+
(1, 2)
347334
348335
julia> Point{Float32}(1, 2)
349-
Point{Float32}(1.0f0, 2.0f0)
336+
(1.0f0, 2.0f0)
350337
```
351338

352339
Note that the default constructors work only if we use arguments with the same type or if we specify the `T` parameter manually. In all other cases, an error will occur.
@@ -365,15 +352,39 @@ This situation can be handled by defining custom constructors, as discussed in t
365352
<header class = "exercise-header">Exercise:</header><p>
366353
```
367354

368-
Some text that describes the exercise
355+
Define a structure that represents 3D-point. Do not forget to define it as a subtype of the AbstractPoint type. Also, add a new method to the `coordinates` function.
369356

370357
```@raw html
371358
</p></div>
372359
<details class = "solution-body">
373360
<summary class = "solution-header">Solution:</summary><p>
374361
```
375362

376-
Solution
363+
Since we did not specify what the structure should look like, there are several possibilities for how to define it. For example, we can define it as a structure with three fields.
364+
365+
```jldoctest structs; output = false
366+
struct Point3D{T <: Real} <: AbstractPoint{T}
367+
x::T
368+
y::T
369+
z::T
370+
end
371+
372+
coordinates(p::Point3D) = (p.x, p.y, p.z)
373+
374+
# output
375+
376+
coordinates (generic function with 2 methods)
377+
```
378+
379+
Note that since the `show` function was defined for the abstract type `AbstractPoint` and uses the `coordinates` function, the custom print is immediately applied to the new type.
380+
381+
```jldoctest structs
382+
julia> Point3D(1, 2, 3)
383+
(1, 2, 3)
384+
385+
julia> Point3D{Float32}(1, 2, 3)
386+
(1.0f0, 2.0f0, 3.0f0)
387+
```
377388

378389
```@raw html
379390
</p></details>
@@ -383,7 +394,7 @@ Solution
383394

384395
Constructors are functions that create new instances of composite types. When the user defines a new composite type, Julia creates default constructors. However, sometimes it is very useful to add additional constructors. As an example, we can mention the case from the end of the previous section. In this case, it makes sense to have the ability to create an instance of the `Point` type from two numbers that can be of any subtypes of `Real`. This can be achieved by defining the following constructor.
385396

386-
```jldoctest structs
397+
```jldoctest structs; output = false
387398
Point(x::Real, y::Real) = Point(promote(x, y)...)
388399
389400
# output
@@ -395,46 +406,95 @@ Note that we use the `promote` function. This function converts its arguments to
395406

396407
```jldoctest structs
397408
julia> Point(1, 2.0)
398-
Point{Float64}(1.0, 2.0)
409+
(1.0, 2.0)
410+
411+
julia> typeof(Point(1, 2.0))
412+
Point{Float64}
399413
```
400414

401415
As expected, the result is of type `Point{Float64}`. The constructor defined above is called an outer constructor because it is defined outside the type definition. A constructor is just like any other function in Julia in that the combined behavior of its methods defines its overall behavior. Accordingly, you can add functionality to a constructor by defining new methods.
402416

403-
Outer constructors can be used to provide additional convenience methods for constructing objects. However, they can not be used to constructing self-referential objects or if we want to ensure that the resulting instance has some special properties. In such a case, we have to use inner constructors. An inner constructor method is like an outer constructor method, except for two differences
417+
Outer constructors can be used to provide additional convenience methods for constructing objects. However, they can not be used to constructing self-referential objects or if we want to ensure that the resulting instance has some special properties. In such a case, we have to use inner constructors. An inner constructor method is like an outer constructor method, except for two differences.
404418

405419
1. It is declared inside the block of a type declaration rather than outside of it like normal methods.
406-
2. It has access to a special locally existent function called new that creates objects of the block's type.
420+
2. It has access to a special locally existent function called `new` that creates objects of the block's type.
407421

408422
For example, suppose one wants to declare a type that holds a pair of real numbers, subject to the constraint that the first number is not greater than the second one. One could declare it like this.
409423

410-
```jldoctest; output = false
411-
struct OrderedPair
424+
```jldoctest ordered; output = false
425+
struct OrderedPair{T <: Real}
412426
x::Real
413427
y::Real
414428
415-
OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
429+
function OrderedPair(x::Real, y::Real)
430+
x > y && error("out of order")
431+
xp, yp = promote(x, y)
432+
return new{typeof(xp)}(xp, yp)
433+
end
416434
end
417435
418436
# output
419437
420438
```
421439

422-
If any inner constructor method is defined, **no default constructor method is provided**. In the example above it means, that any instance of the `OrderedPair` has to meet the assumption, that `x <= y`. Moreover, outer constructor methods can only create objects by calling other constructor methods, i.e., some inner constructor must be called to create an object. It means that even if we add any number of outer constructors, the resulting object is created by the inner constructor and therefore has to meet its assumptions.
440+
If any inner constructor method is defined, **no default constructor method is provided**. In the example above it means, that any instance of the `OrderedPair` has to meet the assumption, that `x <= y`.
441+
442+
```jldoctest ordered
443+
julia> OrderedPair(1,2)
444+
OrderedPair{Int64}(1, 2)
445+
446+
julia> OrderedPair(2,1)
447+
ERROR: out of order
448+
[...]
449+
```
450+
451+
Moreover, outer constructor methods can only create objects by calling other constructor methods, i.e., some inner constructor must be called to create an object. It means that even if we add any number of outer constructors, the resulting object is created by the inner constructor and therefore has to meet its assumptions.
423452

424453
```@raw html
425454
<div class = "exercise-body">
426455
<header class = "exercise-header">Exercise:</header><p>
427456
```
428457

429-
Some text that describes the exercise
458+
Define a structure that represents ND-point and stores coordinates as `NTuple` type. Do not forget to define it as a subtype of the AbstractPoint type. Also, add a new method to the `coordinates` function. Redefine the default inner constructor to allow creating an instance of the `PointND` directly from values of different types.
459+
460+
**Hint:** use the `new` function in the definition of the new inner.
461+
462+
**Hint:** in the `NTuple{N, T}` type `N` represents a number of elements and `T` their type. Use similar notation in the definition of the `PointND` to specify a dimension.
430463

431464
```@raw html
432465
</p></div>
433466
<details class = "solution-body">
434467
<summary class = "solution-header">Solution:</summary><p>
435468
```
436469

437-
Solution
470+
In this case, we can use inner constructor with optional number of input arguments. In the definition belowe, we use type annotation to set, that these arguments have to be real numbers. Note that we the `new` function and we have to specify the value of `N` and type `T`.
471+
472+
```jldoctest structs; output = false
473+
struct PointND{N, T <: Real} <: AbstractPoint{T}
474+
x::NTuple{N, T}
475+
476+
function PointND(args::Real...)
477+
vals = promote(args...)
478+
return new{length(args), eltype(vals)}(vals)
479+
end
480+
end
481+
482+
coordinates(p::PointND) = p.x
483+
484+
# output
485+
486+
coordinates (generic function with 3 methods)
487+
```
488+
489+
Note that since the `show` function was defined for the abstract type `AbstractPoint` and uses the `coordinates` function, the custom print is immediately applied to the new type. Also note, that since we redefined the default constructors, we are able to create instance of the `PointND` type from inputs of mixed types
490+
491+
```jldoctest structs
492+
julia> PointND(1, 2)
493+
(1, 2)
494+
495+
julia> PointND(1, 2.2, 3, 4.5)
496+
(1.0, 2.2, 3.0, 4.5)
497+
```
438498

439499
```@raw html
440500
</p></details>
@@ -444,7 +504,7 @@ Solution
444504

445505
In many cases, it is beneficial to define custom types with default field values. This can be achieved by defining a constructor that uses optional or keyword arguments. Another option is to use the `@kwdef` macro from the `Base`. This macro automatically defines a keyword-based constructor
446506

447-
```jldoctest structs
507+
```jldoctest structs; output = false
448508
Base.@kwdef struct MyType
449509
a::Int # required keyword
450510
b::Float64 = 2.3
@@ -482,44 +542,3 @@ MyType(3, 2.3, "hello")
482542
julia> MyType(; a = 5, b = 4.5)
483543
MyType(5, 4.5, "hello")
484544
```
485-
486-
```@raw html
487-
<div class = "exercise-body">
488-
<header class = "exercise-header">Exercise:</header><p>
489-
```
490-
491-
Some text that describes the exercise
492-
493-
```@raw html
494-
</p></div>
495-
<details class = "solution-body">
496-
<summary class = "solution-header">Solution:</summary><p>
497-
```
498-
499-
Solution
500-
501-
```@raw html
502-
</p></details>
503-
```
504-
505-
## Function-like objects
506-
507-
508-
```@raw html
509-
<div class = "exercise-body">
510-
<header class = "exercise-header">Exercise:</header><p>
511-
```
512-
513-
Some text that describes the exercise
514-
515-
```@raw html
516-
</p></div>
517-
<details class = "solution-body">
518-
<summary class = "solution-header">Solution:</summary><p>
519-
```
520-
521-
Solution
522-
523-
```@raw html
524-
</p></details>
525-
```

0 commit comments

Comments
 (0)