Skip to content
Snippets Groups Projects
Commit e30c0528 authored by YingXing's avatar YingXing
Browse files

update exercise

parent 1bb64008
Branches
No related tags found
1 merge request!3Exercise 3
...@@ -8,3 +8,4 @@ ...@@ -8,3 +8,4 @@
*.jl.*.cov *.jl.*.cov
.ipynb_checkpoints .ipynb_checkpoints
*.gif *.gif
.DS_Store
%% Cell type:code id:b7689bbe tags:
``` julia
# import dependencies
using Test
```
%% Cell type:markdown id:debc3515-fa08-4577-82bb-9eced849d616 tags:
# Data Types and Multiple Dispatch
In this notebook, we will explore the fundamental concepts of Julia's type system and understand multiple dispatch, focusing on:
- Abstract and Concrete Types
- Dynamic Types
- Multiple Dispatch
These concepts enhance Julia's performance, especially in mathematical and scientific computing.
%% Cell type:markdown id:66c9e24b-965f-490e-873f-83b4d654d4f2 tags:
## Types: Abstract and Concrete
Julia's type system includes **abstract types** and **concrete types**.
- **Abstract Types**: Cannot be instantiated and serve to represent general categories of objects.
- **Concrete Types**: Can be instantiated and are used to create objects.
%% Cell type:code id:3885f36f-1ee4-4fa3-aaa0-3df62bcac362 tags:
``` julia
abstract type Animal end # Abstract type
struct Dog <: Animal # Concrete type inheriting from Animal
name::String
age::Int
end
struct Cat <: Animal # Another concrete type inheriting from Animal
name::String
age::Int
end
a_dog = Dog("Buddy", 3)
a_cat = Cat("Kitty", 2)
```
%% Cell type:markdown id:aa33841d tags:
One can use subtypes to obtain all subtypes of either an abstract type or a concrete type.
%% Cell type:code id:9a30f7e1 tags:
``` julia
subtypes(Animal)
```
%% Cell type:markdown id:06bb4657 tags:
We can write a nice function to make the output nicer:
%% Cell type:code id:a5204ffa tags:
``` julia
"""
print_type_tree(T, level=0, max_level=typemax(Int), prefix="", subtype_prefix="")
Print a tree structure that visualizes the type hierarchy rooted at `T`.
# Parameters
- `T`: The type which serves as the root of the tree to print.
- `level`: (Optional, default=`0`) An integer specifying the current recursion depth - typically not provided by the user.
- `max_level`: (Optional, default=`typemax(Int)`) An integer specifying the maximum recursion depth, i.e., how many levels deep into the type hierarchy to print.
- `prefix`: (Optional, default=`""`) A string used internally to format the tree structure - typically not provided by the user.
- `subtype_prefix`: (Optional, default=`""`) A string used internally to format the tree structure - typically not provided by the user.
# Usage
```julia
print_type_tree(Number, max_level=7)
```
"""
function print_type_tree(T, level=0, max_level=typemax(Int), prefix="", subtype_prefix="")
# Stop recursion if max level is reached
if level >= max_level
return
end
# Print current type
println(prefix, subtype_prefix, T)
# Obtain subtypes
subs = subtypes(T)
# Recursively print subtypes with adjusted prefix
for (i, subtype) in enumerate(subs)
new_prefix = " "
subtype_prefix = i < length(subs) ? "├── " : "└── "
# If the subtype has no further subtypes, avoid using the "|" in the prefix
if isempty(subtypes(subtype))
print_type_tree(subtype, level+1, max_level, prefix * " ", subtype_prefix)
else
print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix)
end
end
end
```
%% Cell type:code id:ba91649d tags:
``` julia
print_type_tree(Animal)
```
%% Cell type:markdown id:87a9c84f tags:
We can also use this function to print the subtypes of built-in objects.
%% Cell type:code id:53919ada tags:
``` julia
print_type_tree(Number)
```
%% Cell type:code id:1e23272e tags:
``` julia
# Set max_level to 2 to avoid lengthy output.
print_type_tree(AbstractArray, 0, 2)
```
%% Cell type:markdown id:16d371f7 tags:
For concrete structs, constructors are special functions that instantiate objects of a particular type, initiating the lifecycle of an object instance.
When an object of a custom type is created, it is initialized via constructors, ensuring that it begins its existence in a valid state.
### Default Constructors
The name of a constructor is the same as the name of the type and can have multiple methods, which differ in their argument types.
By default, Julia provides a constructor that accepts arguments for all fields, unless an `inner constructor` is provided, in which case you need to define all needed constructors yourself.
For instance, if you have a type named `MyType`, the constructor(s) for this type will also be named `MyType`.
%% Cell type:code id:277c436a tags:
``` julia
struct MyType1
a::Int
b::Float64
end
# The default constructor is MyType1
obj_1 = MyType1(1, 2.0)
@testset "Test MyType1" begin
# Testing with some valid inputs
obj_1 = MyType1(1, 2.0)
@test obj_1.a == 1
@test obj_1.b == 2.0
end
```
%% Cell type:markdown id:2051b0ac tags:
### Inner and Outer Constructors
Julia features inner and outer constructors. Inner constructors are defined inside the block of the type definition and have access to its private internals, while outer constructors are defined outside and call an inner constructor to create an object.
%% Cell type:code id:51d774f8 tags:
``` julia
struct MyType2
a::Int
b::Float64
# Inner constructor
function MyType2(a, b)
a < 0 && throw(ArgumentError("a must be non-negative"))
new(a, b)
end
end
# Outer constructor
MyType2(a::Int) = MyType2(a, 0.0)
```
%% Cell type:code id:b859da95 tags:
``` julia
@testset "Test MyType2" begin
@testset "Innter constructor with valid inputs" begin
obj_21 = MyType2(100, 11.34)
@test obj_21.a == 100
@test obj_21.b == 11.34
end
@testset "Inner constructor with invalid inputs" begin
@test_throws ArgumentError MyType2(-100, 1.0)
end
@testset "Outer constructor with valid inputs" begin
obj_22 = MyType2(2)
@test obj_22.a == 2
@test obj_22.b == 0.0
end
@testset "Outer constructor with invalid inputs" begin
@test_throws ArgumentError MyType2(-1)
end
end
```
%% Cell type:markdown id:e65f0f9a tags:
In the example, the inner constructor validates that `a` is non-negative, providing a measure of control and validation over the initialization process.
%% Cell type:markdown id:bde87d55 tags:
### Parametric Constructors
Parametric types can also have constructors, with parameters specified in the type definition, offering greater flexibility.
%% Cell type:code id:9e5c0f17 tags:
``` julia
struct MyType3{T1<:Real, T2<:Real}
a::T1
b::T2
# Inner constructor
function MyType3(a::T1, b::T2) where {T1, T2}
a < 0 && throw(ArgumentError("a must be non-negative"))
new{T1, T2}(a, b)
end
end
```
%% Cell type:code id:58e3d497 tags:
``` julia
@testset "Test MyType3" begin
@testset "Valid inputs" begin
obj_31 = MyType3(1, 2.0)
@test obj_31.a == 1
@test obj_31.b == 2.0
obj_32 = MyType3(1.0, 2.0)
@test obj_32.a == 1.0
@test obj_32.b == 2.0
obj_33 = MyType3(1, 2)
@test obj_33.a == 1
@test obj_33.b == 2
end
end
```
%% Cell type:markdown id:8b60210e tags:
Exercise: `MyType3` can only take a real number. Please write `MyType4` to improve it, using the guidelines below:
- Accept `Number` as inputs.
- Modify the inner constructor to handle `Complex` numbers for argument `a`. Specifically, if a complex number is supplied, bypass any checking mechanisms.
- Add an outer constructor like in `MyType3`.
%% Cell type:code id:4edd8d4e tags:
``` julia
# TODO
struct MyType4
a
b
end
```
%% Cell type:markdown id:59da2faf tags:
The new MyType4 should pass all of the following tests:
%% Cell type:code id:d1cfb791 tags:
``` julia
@testset "Test MyType4" begin
@testset "Inner constructor with valid inputs" begin
obj_41 = MyType4(1, 2.0)
@test obj_41.a == 1
@test obj_41.b == 2.0
obj_42 = MyType4(1.0, 2.0)
@test obj_42.a == 1.0
@test obj_42.b == 2.0
obj_43 = MyType4(1, 2)
@test obj_43.a == 1
@test obj_43.b == 2
obj_44 = MyType4(Complex(1.0, 1.0), 2)
@test obj_44.a == 1.0 + 1.0im
@test obj_44.b == 2
end
@testset "Inner constructor with invalid inputs" begin
@test_throws ArgumentError MyType4(-1, 1)
end
@testset "Outer constructor with valid inputs" begin
@test_throws ArgumentError MyType4(1)
end
@testset "Outer constructor with invalid inputs" begin
@test_throws ArgumentError MyType4(-1)
end
end
```
%% Cell type:markdown id:6bd93a18-6569-4253-a836-0601b77c8bf2 tags:
## Dynamical Types
%% Cell type:markdown id:c7784fbb tags:
Dynamical types in Julia allow variables to change their type dynamically.
Julia uses `dynamic typing`, which means that the type of a variable is checked at runtime.
%% Cell type:code id:a77d6d3c-e838-45c7-bd71-249c8547281e tags:
``` julia
x = Dog("Buddy", 4) # x is an Dog
println(typeof(x))
x = Cat("Kitty", 3) # Now x is a Cat
println(typeof(x))
```
%% Cell type:markdown id:bb3f86ee-ec12-41a4-b29c-a622bc95af8d tags:
## Multiple Dispatch of Functions
%% Cell type:markdown id:37ef5d37 tags:
Multiple dispatch is one of Julia's key features.
It allows defining function behavior across many combinations of argument types.
Different method definitions are dispatched based on the types of all function arguments.
%% Cell type:code id:6e409d52-290d-4e47-a4ba-5030ff45b66c tags:
``` julia
# Define a generic function speak
speak(animal::Animal) = "Some generic animal noise"
# Use multiple dispatch to define method for specific types
speak(animal::Dog) = "Woof! I am $(animal.name), a dog."
speak(animal::Cat) = "Meow! I am $(animal.name), a cat."
# Test the methods
println(speak(a_dog))
println(speak(a_cat))
```
%% Cell type:markdown id:e28a025f tags:
## Union Types
%% Cell type:markdown id:9dd9a6fa tags:
Union types are a useful feature in Julia that allow a variable to take on values of several different types.
It allows more flexibility when dealing with variables that might have different types in different situations.
%% Cell type:code id:9a11afbb tags:
``` julia
const CatOrDog = Union{Cat, Dog}
function process_input(input::CatOrDog)
speak(input)
end
process_input(a_dog)
process_input(a_cat)
```
%% Cell type:markdown id:5117e6dc tags:
## Parametric Types
%% Cell type:markdown id:6fddf5da tags:
Parametric types allow you to define types that are parameterized by other types.
It provides the ability to create a function or type that works with different data types.
%% Cell type:code id:daa25661 tags:
``` julia
struct Point{T}
x::T
y::T
end
p1 = Point{Int}(1, 2)
p2 = Point{Float64}(1.0, 2.0)
p3 = Point{String}("one", "two")
println(p1)
println(p2)
println(p3)
```
%% Cell type:markdown id:408f1815 tags:
## Function Overloading
%% Cell type:markdown id:1b83770e tags:
Function overloading allows you to define different versions of a function for different types or numbers of arguments.
It provides flexibility and improves code readability and performance.
%% Cell type:code id:af6ad638 tags:
``` julia
add(x::Dog, y::Cat) = x.age + y.age
add(x::Cat, y::Dog) = x.age + y.age
add(x::Dog, y::Dog) = x.age + y.age
add(x::Cat, y::Cat) = x.age + y.age
println(add(a_dog, a_cat)) # Calls the first version
println(add(a_cat, a_dog)) # Calls the second version
```
%% Cell type:markdown id:7f2723a1 tags:
The function overloading above can be simplified by the following one using generic function.
%% Cell type:code id:853117bc tags:
``` julia
add(x::Animal, y::Animal) = x.age + y.age
```
%% Cell type:code id:705971b5 tags:
``` julia
struct Tiger<:Animal
name::String
age::Int
end
a_tiger = Tiger("Ravi", 3)
print(add(a_tiger, a_dog))
```
%% Cell type:code id:907ced09 tags:
``` julia
```
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment