You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<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
-
252
233
## Parametric types
253
234
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.
255
236
256
237
```jldoctest structs; output = false
257
238
abstract type AbstractPoint{T} end
@@ -278,20 +259,14 @@ true
278
259
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.
279
260
280
261
```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
288
263
true
289
264
290
-
julia> Point{Int64} <: AbstractPoint
265
+
julia> Point{Int64} <: Point <: AbstractPoint
291
266
true
292
267
```
293
268
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}`.
295
270
296
271
```jldoctest structs
297
272
julia> Point{Float64} <: Point{Real}
@@ -306,47 +281,59 @@ false
306
281
307
282
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:
308
283
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.
310
285
- 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.
311
286
312
287
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.
313
288
314
289
Since `Point{Float64}` is not a subtype of `Point{Real}`, the following method can't be applied to arguments of type `Point{Float64}`.
315
290
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)
319
293
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})
322
296
[...]
323
297
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})
326
300
[...]
327
301
```
328
302
303
+
329
304
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.
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.
343
330
344
331
```jldoctest structs
345
332
julia> Point(1, 2)
346
-
Point{Int64}(1, 2)
333
+
(1, 2)
347
334
348
335
julia> Point{Float32}(1, 2)
349
-
Point{Float32}(1.0f0, 2.0f0)
336
+
(1.0f0, 2.0f0)
350
337
```
351
338
352
339
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
365
352
<header class = "exercise-header">Exercise:</header><p>
366
353
```
367
354
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.
369
356
370
357
```@raw html
371
358
</p></div>
372
359
<details class = "solution-body">
373
360
<summary class = "solution-header">Solution:</summary><p>
374
361
```
375
362
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
+
```
377
388
378
389
```@raw html
379
390
</p></details>
@@ -383,7 +394,7 @@ Solution
383
394
384
395
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.
385
396
386
-
```jldoctest structs
397
+
```jldoctest structs; output = false
387
398
Point(x::Real, y::Real) = Point(promote(x, y)...)
388
399
389
400
# output
@@ -395,46 +406,95 @@ Note that we use the `promote` function. This function converts its arguments to
395
406
396
407
```jldoctest structs
397
408
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}
399
413
```
400
414
401
415
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.
402
416
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.
404
418
405
419
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.
407
421
408
422
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.
409
423
410
-
```jldoctest; output = false
411
-
struct OrderedPair
424
+
```jldoctest ordered; output = false
425
+
struct OrderedPair{T <: Real}
412
426
x::Real
413
427
y::Real
414
428
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
416
434
end
417
435
418
436
# output
419
437
420
438
```
421
439
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.
423
452
424
453
```@raw html
425
454
<div class = "exercise-body">
426
455
<header class = "exercise-header">Exercise:</header><p>
427
456
```
428
457
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.
430
463
431
464
```@raw html
432
465
</p></div>
433
466
<details class = "solution-body">
434
467
<summary class = "solution-header">Solution:</summary><p>
435
468
```
436
469
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
+
```
438
498
439
499
```@raw html
440
500
</p></details>
@@ -444,7 +504,7 @@ Solution
444
504
445
505
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
446
506
447
-
```jldoctest structs
507
+
```jldoctest structs; output = false
448
508
Base.@kwdef struct MyType
449
509
a::Int # required keyword
450
510
b::Float64 = 2.3
@@ -482,44 +542,3 @@ MyType(3, 2.3, "hello")
482
542
julia> MyType(; a = 5, b = 4.5)
483
543
MyType(5, 4.5, "hello")
484
544
```
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>
0 commit comments