Skip to content
Snippets Groups Projects
Commit 437b6d6a authored by Michele Nottoli's avatar Michele Nottoli
Browse files

Update exc. 3.

parent 11f89c4e
No related branches found
No related tags found
1 merge request!3Exercise 3
%% Cell type:markdown id:debc3515-fa08-4577-82bb-9eced849d616 tags: %% Cell type:markdown id:debc3515-fa08-4577-82bb-9eced849d616 tags:
# Data Types and Multiple Dispatch # 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: In this notebook, we will explore the fundamental concepts of Julia's type system and understand multiple dispatch, focusing on:
- Abstract and Concrete Types - Abstract and Concrete Types
- Dynamic Types - Dynamic Types
- Multiple Dispatch - Multiple Dispatch
These concepts enhance Julia's performance, especially in mathematical and scientific computing. These concepts enhance Julia's performance, especially in mathematical and scientific computing.
%% Cell type:markdown id:7128c447 tags: %% Cell type:markdown id:7128c447 tags:
## Abstract and concrete types ## Abstract and concrete types
%% Cell type:markdown id:723212c2 tags: %% Cell type:markdown id:723212c2 tags:
Before we discuss multiple dispatch of functions and dispatch by types, we briefly review Julia's type system. Before we discuss multiple dispatch of functions and dispatch by types, we briefly review Julia's type system.
Types in Julia fall into two categories: **Abstract** and **concrete**. Types in Julia fall into two categories: **Abstract** and **concrete**.
- **Abstract Types**: Cannot be instantiated and serve to represent general categories of objects. - **Abstract Types**: Cannot be instantiated and serve to represent general categories of objects.
- **Concrete Types**: Can be instantiated and are used to create objects. - **Concrete Types**: Can be instantiated and are used to create objects.
%% Cell type:markdown id:58c7dc0c tags: %% Cell type:markdown id:58c7dc0c tags:
In Julia, there are several built-in functions related to querying and working with types and `DataType``. In Julia, there are several built-in functions related to querying and working with types.
Here is a selection of some important ones: Here is a selection of some important ones:
- `<:`: The subtype operator used to check if a type is a subtype of another type. - `<:`: The subtype operator used to check if a type is a subtype of another type.
- `isabstracttype(T)`: Check if T is an abstract type. - `isabstracttype(T)`: Check if T is an abstract type.
- `isconcretetype(T)`: Check if T is a concrete type. - `isconcretetype(T)`: Check if T is a concrete type.
- `subtypes(T)`: Get a list of all immediate subtypes of type T. - `subtypes(T)`: Get a list of all immediate subtypes of type T.
- `supertype(T)`: Get the direct supertype of type T. - `supertype(T)`: Get the direct supertype of type T.
%% Cell type:markdown id:00533545 tags: %% Cell type:markdown id:00533545 tags:
Abstract types such as `Integer` or `Number` are supertypes of a bunch of other types, for example: Abstract types such as `Integer` or `Number` are supertypes of a bunch of other types, for example:
%% Cell type:code id:50b00765 tags: %% Cell type:code id:50b00765 tags:
``` julia ``` julia
@show Int32 <: Integer # Read Int32 is a sub-type of Integer @show Int32 <: Integer # Read Int32 is a sub-type of Integer
@show UInt16 <: Integer # UInt16 is a sub-type of Integer @show UInt16 <: Integer # UInt16 is a sub-type of Integer
@show Float32 <: Integer # Float32 is not a sub-type of Integer @show Float32 <: Integer # Float32 is not a sub-type of Integer
@show Float32 <: Number # Float32 is a sub-type of Number @show Float32 <: Number # Float32 is a sub-type of Number
@show Integer <: Number; # Integer is a sub-type of Nummber @show Integer <: Number; # Integer is a sub-type of Nummber
``` ```
%% Cell type:code id:329380e7 tags: %% Cell type:code id:329380e7 tags:
``` julia ``` julia
# by transitivity: # by transitivity:
@show Int32 <: Number @show Int32 <: Number
@show UInt16 <: Number @show UInt16 <: Number
@show Number <: Number; @show Number <: Number;
``` ```
%% Cell type:markdown id:aa54bfef tags: %% Cell type:markdown id:aa54bfef tags:
### Type properties ### Type properties
We can check type properties in various ways: We can check type properties in various ways:
%% Cell type:code id:49228132 tags: %% Cell type:code id:49228132 tags:
``` julia ``` julia
@show isconcretetype(Int32); @show isconcretetype(Int32);
``` ```
%% Cell type:code id:9ffd1775 tags: %% Cell type:code id:9ffd1775 tags:
``` julia ``` julia
@show isabstracttype(Integer); @show isabstracttype(Integer);
``` ```
%% Cell type:markdown id:a778662d tags: %% Cell type:markdown id:a778662d tags:
A fancy way is even to display a type tree: A fancy way is even to display a type tree:
%% Cell type:code id:f5a1fd53 tags: %% Cell type:code id:f5a1fd53 tags:
``` julia ``` julia
""" """
print_type_tree(T, level=0, max_level=typemax(Int), prefix="", subtype_prefix="") 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`. Print a tree structure that visualizes the type hierarchy rooted at `T`.
# Parameters # Parameters
- `T`: The type which serves as the root of the tree to print. - `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. - `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. - `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. - `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. - `subtype_prefix`: (Optional, default=`""`) A string used internally to format the tree structure - typically not provided by the user.
# Usage # Usage
```julia ```julia
print_type_tree(Number, max_level=7) print_type_tree(Number, max_level=7)
``` ```
""" """
function print_type_tree(T, level=0, max_level=typemax(Int), prefix="", subtype_prefix="") function print_type_tree(T, level=0, max_level=typemax(Int), prefix="", subtype_prefix="")
# Stop recursion if max level is reached # Stop recursion if max level is reached
if level >= max_level if level >= max_level
return return
end end
# Print current type # Print current type
println(prefix, subtype_prefix, T) println(prefix, subtype_prefix, T)
# Obtain subtypes # Obtain subtypes
subs = subtypes(T) subs = subtypes(T)
# Recursively print subtypes with adjusted prefix # Recursively print subtypes with adjusted prefix
for (i, subtype) in enumerate(subs) for (i, subtype) in enumerate(subs)
new_prefix = " " new_prefix = " "
subtype_prefix = i < length(subs) ? "├── " : "└── " subtype_prefix = i < length(subs) ? "├── " : "└── "
# If the subtype has no further subtypes, avoid using the "|" in the prefix # If the subtype has no further subtypes, avoid using the "|" in the prefix
if isempty(subtypes(subtype)) if isempty(subtypes(subtype))
print_type_tree(subtype, level+1, max_level, prefix * " ", subtype_prefix) print_type_tree(subtype, level+1, max_level, prefix * " ", subtype_prefix)
else else
print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix) print_type_tree(subtype, level+1, max_level, prefix * new_prefix, subtype_prefix)
end end
end end
end end
``` ```
%% Cell type:code id:41f4de49 tags: %% Cell type:code id:41f4de49 tags:
``` julia ``` julia
print_type_tree(Number) print_type_tree(Number)
``` ```
%% Cell type:markdown id:c3722ed0 tags: %% Cell type:markdown id:c3722ed0 tags:
**Exercise**: Write code to determine all subtypes of `Number`, whether they are abstract or concrete. **Exercise**: Write code to determine all subtypes of `Number`, whether they are abstract or concrete.
%% Cell type:code id:39b8339f tags: %% Cell type:code id:39b8339f tags:
``` julia ``` julia
# TODO: implement your code # TODO: implement your code
``` ```
%% Cell type:markdown id:dacc835e tags: %% Cell type:markdown id:dacc835e tags:
To get the super type of a type, one can use `supertype`: To get the super type of a type, one can use `supertype`:
%% Cell type:code id:e666712d tags: %% Cell type:code id:e666712d tags:
``` julia ``` julia
@show supertype(Int64) @show supertype(Int64)
@show supertype(Signed) @show supertype(Signed)
@show supertype(Float16) @show supertype(Float16)
@show supertype(Float32) @show supertype(Float32)
@show supertype(Bool); @show supertype(Bool);
``` ```
%% Cell type:code id:147db9b0 tags: %% Cell type:code id:147db9b0 tags:
``` julia ``` julia
function print_supertypes(T::Type) function print_supertypes(T::Type)
println("Supertypes of $T:") println("Supertypes of $T:")
while T != Any while T != Any
print(T, " ---> ") print(T, " ---> ")
T = supertype(T) T = supertype(T)
end end
println(T) # Print Any, which is the ultimate supertype of all types println(T) # Print Any, which is the ultimate supertype of all types
end end
``` ```
%% Cell type:code id:ed566f15 tags: %% Cell type:code id:ed566f15 tags:
``` julia ``` julia
print_supertypes(Int64); print_supertypes(Int64);
``` ```
%% Cell type:code id:1462bc0c tags: %% Cell type:code id:1462bc0c tags:
``` julia ``` julia
print_supertypes(Float64); print_supertypes(Float64);
``` ```
%% Cell type:markdown id:808b7968 tags: %% Cell type:markdown id:808b7968 tags:
### Custom Abstract And Concrete Types ### Custom Abstract And Concrete Types
%% Cell type:markdown id:8cbad09b tags: %% Cell type:markdown id:8cbad09b tags:
One can define custom abstract type by using `abstract type`: One can define custom abstract type by using `abstract type`:
%% Cell type:code id:3ebf46f6 tags: %% Cell type:code id:3ebf46f6 tags:
``` julia ``` julia
abstract type Animal end # Abstract type abstract type Animal end # Abstract type
``` ```
%% Cell type:markdown id:c1cceba1 tags: %% Cell type:markdown id:c1cceba1 tags:
Then, some concrete types can be created as well: Then, some concrete types can be created as well:
%% Cell type:code id:eaab065e tags: %% Cell type:code id:eaab065e tags:
``` julia ``` julia
struct Dog <: Animal # Concrete type inheriting from Animal struct Dog <: Animal # Concrete type inheriting from Animal
name::String name::String
age::Int age::Int
end end
struct Cat <: Animal # Another concrete type inheriting from Animal struct Cat <: Animal # Another concrete type inheriting from Animal
name::String name::String
age::Int age::Int
end end
``` ```
%% Cell type:markdown id:747d5555 tags: %% Cell type:markdown id:747d5555 tags:
In this example, we create two concrete animal, `Dog` and `Cat`. In this example, we create two concrete animal, `Dog` and `Cat`.
One can use subtypes to obtain all subtypes of either an abstract type or a concrete type. One can use subtypes to obtain all subtypes of either an abstract type or a concrete type.
%% Cell type:code id:b8b7fca8 tags: %% Cell type:code id:b8b7fca8 tags:
``` julia ``` julia
subtypes(Animal) subtypes(Animal)
``` ```
%% Cell type:markdown id:466cfcf7 tags: %% Cell type:markdown id:466cfcf7 tags:
Again, using `isabstracttype` and `isconcretetype` we have Again, using `isabstracttype` and `isconcretetype` we have
%% Cell type:code id:3eced325 tags: %% Cell type:code id:3eced325 tags:
``` julia ``` julia
@show isabstracttype(Animal) @show isabstracttype(Animal)
@show isabstracttype(Dog) @show isabstracttype(Dog)
@show isabstracttype(Cat) @show isabstracttype(Cat)
@show isconcretetype(Animal) @show isconcretetype(Animal)
@show isconcretetype(Dog) @show isconcretetype(Dog)
@show isconcretetype(Cat); @show isconcretetype(Cat);
``` ```
%% Cell type:markdown id:580bf73c tags: %% Cell type:markdown id:580bf73c tags:
The type tree of `Animal` is: The type tree of `Animal` is:
%% Cell type:code id:40debabd tags: %% Cell type:code id:40debabd tags:
``` julia ``` julia
print_type_tree(Animal) print_type_tree(Animal)
``` ```
%% Cell type:markdown id:f4fb75fe tags: %% Cell type:markdown id:f4fb75fe tags:
Now, we create two instances from the concrete types: Now, we create two instances from the concrete types:
%% Cell type:code id:6f0f092f tags: %% Cell type:code id:6f0f092f tags:
``` julia ``` julia
a_dog = Dog("Buddy", 3) a_dog = Dog("Buddy", 3)
a_cat = Cat("Kitty", 2) a_cat = Cat("Kitty", 2)
@show a_dog a_cat;
``` ```
%% Cell type:markdown id:19797a26 tags: %% Cell type:markdown id:19797a26 tags:
In Julia, the `isa` method is used to determine whether an instance is of a particular type, whether it is abstract or concrete: In Julia, the `isa` method is used to determine whether an instance is of a particular type, whether it is abstract or concrete:
%% Cell type:code id:263461b1 tags: %% Cell type:code id:263461b1 tags:
``` julia ``` julia
@show isa(a_dog, Dog) @show isa(a_dog, Dog)
@show isa(a_dog, Animal) @show isa(a_dog, Animal)
@show isa(a_dog, Cat) @show isa(a_dog, Cat)
@show isa(a_cat, Cat); @show isa(a_cat, Cat);
``` ```
%% Cell type:markdown id:cbb0a853 tags: %% Cell type:markdown id:cbb0a853 tags:
The method `typeof` is used to obtain the type of an instance: The method `typeof` is used to obtain the type of an instance:
%% Cell type:code id:208aed02 tags: %% Cell type:code id:208aed02 tags:
``` julia ``` julia
@show typeof(a_dog) @show typeof(a_dog)
@show typeof(a_cat); @show typeof(a_cat);
``` ```
%% Cell type:markdown id:1be97baf tags: %% Cell type:markdown id:1be97baf tags:
We can also get all supertypes of `Dog` and `Cat`: We can also get all supertypes of `Dog` and `Cat`:
%% Cell type:code id:b3ca83e6 tags: %% Cell type:code id:b3ca83e6 tags:
``` julia ``` julia
print_supertypes(Dog) print_supertypes(Dog)
``` ```
%% Cell type:code id:662a732f tags: %% Cell type:code id:662a732f tags:
``` julia ``` julia
print_supertypes(Cat) print_supertypes(Cat)
``` ```
%% Cell type:markdown id:ff7efe19 tags: %% Cell type:markdown id:ff7efe19 tags:
### Exercise ### Exercises
Write code to implement some abstract and concrete subtypes of `Animal`, and use `print_type_tree` to view the type tree. Write code to implement some abstract and concrete subtypes of `Animal`, and use `print_type_tree` to view the type tree. For example you can implement the abstract type `Bird` and then a few kinds of birds.
%% Cell type:code id:2fbfbb88 tags: %% Cell type:code id:2fbfbb88 tags:
``` julia ``` julia
# TODO: implement your code # TODO: implement your code
``` ```
%% Cell type:markdown id:9efaf879 tags: %% Cell type:markdown id:52b60c4e-1d3f-46f6-87a9-c3ab2917d8d0 tags:
In Julia concrete types are always a leaf of the type tree, i.e. they cannot be inherited from each other. For a C++ or Python person (as I was before looking into Julia) this seems restrictive at first, but it takes away a lot of unnecessary complexity from the type hierachy. In Julia the structure of a library or a problem
is in many cases not converted into explict type hierachies,
as it would for OOP languages like Python or Java.
Instead it builds up implicitly from conventions which are associated with abstract or concrete types.
For example, if one implements a concrete type for the abstract type `Number` one is expected to implement a certain set of functions (e.g. `*`, `+`, `-`, `/`, ...). Otherwise not all of the standard library and other linear algebra packages will work. The difference to a hard enforcement of interfaces is, however, that *some things* will still work. This has disadvantages as your code could break in the future, but it is extremely useful for rapidly trying something out.
More details see: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types).
%% Cell type:markdown id:ccf57f28 tags:
# Dynamical typing and type deduction
%% Cell type:markdown id:85870a09 tags:
In programming language theory type systems traditionally fall in two categories. Which of the following type are subtypes of another?
In **dynamically typed** languages the type of Try to guess first and then verify by using the operator `<:`.
a value or expression is inferred only at runtime,
which usually allows for more flexible code. Examples are Python or MATLAB.
In contrast, so-called **statically-typed** languages (think FORTRAN or C++),
require types to be already known before runtime when the program is compiled.
This allows both to check more thoroughly for errors (which can manifest in mismatched types)
and it usually brings a gain in performance because more things about the memory layout of the program is known
at compile time. As a result aspects such as vectorisation, contiguous alignment of data,
preallocation of memory can be leveraged more easily.
Julia is kind of both. Strictly speaking it is dynamically typed. E.g. the type of variables can change type at any point: ```julia
Float64 AbstractFloat Integer
Number AbstractArray Complex
Real Any Nothing
```
%% Cell type:code id:3ef4e3d0 tags: %% Cell type:code id:688afde5-1123-4adc-affd-687b966f387e tags:
``` julia ``` julia
x = Dog("Buddy", 4) # x is an Dog # TODO: implement your code
@show typeof(x)
x = Cat("Kitty", 3) # Now x is a Cat
@show typeof(x);
``` ```
%% Cell type:markdown id:a02c0a37 tags: %% Cell type:markdown id:9efaf879 tags:
Note, however, that the type of a *value* cannot change in Julia! In Julia concrete types are always a leaf of the type tree, i.e. they cannot be inherited from each other. For a C++ or Python person (as a few of us) this seems restrictive at first, but it takes away a lot of unnecessary complexity from the type hierarchy. We will not give further information now, the reason will be more clear at the end of this notebook.
Still, Julia's strong emphasis on types are one of the reasons for its performance. More details see: [https://docs.julialang.org/en/v1/base/base/#Properties-of-Types](https://docs.julialang.org/en/v1/base/base/#Properties-of-Types).
Unlike in statically typed languages, however, **type deduction in Julia** happens at runtime, right before JIT-compiling a function: The more narrowly the types for a particular piece of code can be deduced, the better the emitted machine code can be optimised. One can influence this using explicit type annotations in function arguments and intermediate expressions. Due to the to the excellent type inference capabilities of Julia, this is in general not needed, however.
This might sound a bit unusal at first, but the result is, %% Cell type:markdown id:20a01368-ff73-4992-afae-792962aee09f tags:
that it takes almost no effort to write generic code as we will see later: Just leave off all the type annotations. Notice, that this only means that the code has no types. At runtime types are still inferred as much as possible, such that aspects like vectorisation, contiguous alignment of data, preallocation of memory *can* be taken into account by the Julia compiler.
%% Cell type:markdown id:27812692 tags: ## Multiple dispatch
Three more facts about Julia types: Multiple dispatch is probably the key feature of Julia that makes it different with respect to many other languages and give it the ability to be at the same time flexible and performant.
- In Julia all types are the same. For example, there is no difference between `Int32` and `String`, even though the first has a direct mapping to low-level instructions in the CPU and the latter has not (contrast this with e.g. C++).
- The `Nothing` type with the single instance `nothing` is the Julia equivalent to `void` in C++ or `None` in Python. It often represents that a function does not return anything or that a variable has not been initialised.
- `Any` is the root of the type tree: Any type in Julia is a subtype of `Any`.
%% Cell type:markdown id:0a6002c9 tags: To fully understand the concept of multiple dispatch, we will use an analogy, we will pretend that a programming language is a tool to write a book of food recipes. A recipe combines a method of cooking (for example baking, frying) with an ingredient (for example potatoes, carrots, fish).
### Exercise - The first possibility is to organize the book according to methods of cooking: each chapter explains in detail a method of cooking. For example, we will have **Chapter 1: baking**, **Chapter 2: frying** and so on. The drawback of this approach is that whenever we add a new ingredient, we have to change multiple chapters. This approach, focused on the action rather than on ingredients, is typical of functional programming languages.
Which of the following type are subtypes of another?
Try to guess first and then verify by using the operator `<:`.
```julia - The second possibility is to organize the book according to ingredients: each chapter focuses on one ingredient. For example, we will have **Chapter 1: potatoes**, **Chapter 2: fish** and so on. The drawback of this approach is that whenever we add a new recipe, we have again to change multiple chapters. This approach is focused on the ingredients (data) and it is typical of object-oriented programming, where we will have something like:
Float64 AbstractFloat Integer ``` python
Number AbstractArray Complex class potatoes()
Real Any Nothing potatoes.bake()
``` potatoes.fry()
```
%% Cell type:markdown id:ebc902af tags:
##### For more details
https://docs.julialang.org/en/v1/manual/types/
%% Cell type:markdown id:25a35122 tags: - Julia takes a third approach called **multiple dispatch** which decouples the action from the data. In our hypothetical recipe book, we will have chapters like **Chapter 1: baking potatoes**, **Chapter 2: frying potatoes**, **Chapter 3: baking fish**, **Chapter 4: frying fish** and so on. Each of the chapters will contain something like:
``` julia
function baking(potatoes::Potatoes)
function frying(potatoes::Potatoes)
function baking(fish::Fish)
function frying(fish::Fish)
```
## Multiple dispatch In this way, adding a new recipe for a specific kind of food does not require changing already written things.
%% Cell type:markdown id:eec2ef90 tags: %% Cell type:markdown id:a0a6aecb-bd29-472c-921b-c4135a09b026 tags:
Let us return back to the `mymult` function: Let us return back to the `mymult` function and see how we can use the multiple dispatch to implement new functionality:
%% Cell type:code id:c43dc846 tags: %% Cell type:code id:c43dc846 tags:
``` julia ``` julia
mymult(x, y) = x * y mymult(x, y) = x * y
``` ```
%% Cell type:markdown id:d99de7cd tags: %% Cell type:markdown id:d27da057-cb24-45a2-b190-23ef6c8207fa tags:
We were able to safely use this functions with a number of type combinations, but some things do not yet work: We were able to safely use this functions with a number of type combinations, but some things do not yet work:
%% Cell type:code id:65dbe501 tags: %% Cell type:code id:65dbe501 tags:
``` julia ``` julia
mymult(2, " abc") mymult(2, " abc")
``` ```
%% Cell type:markdown id:dccf4b43 tags: %% Cell type:markdown id:6757d995-cbc8-4c7b-be3a-fbdd9c8b984c tags:
Let's say we wanted to concatenate the string `str` $n$ times on multiplication with an integer $n$. In Julia this functionality is already implemented by the exponentiation operator: Let's say we wanted to concatenate the string `str` $n$ times on multiplication with an integer $n$. In Julia this functionality is already implemented by the exponentiation operator:
%% Cell type:code id:8c18999b tags: %% Cell type:code id:8c18999b tags:
``` julia ``` julia
"abc"^4 "abc"^4
``` ```
%% Cell type:markdown id:5d2af9cb tags: %% Cell type:markdown id:ee1f8e46-3b4d-4f63-8e14-44ff70d7b5cf tags:
But for the sake of argument, assume we wanted `mymult("abc", 4)` and `mymult(4, "abc")` to behave the same way. We define two special methods: But for the sake of argument, assume we wanted `mymult("abc", 4)` and `mymult(4, "abc")` to behave the same way. We define two special methods:
%% Cell type:code id:fa0af502 tags: %% Cell type:code id:fa0af502 tags:
``` julia ``` julia
mymult(str::AbstractString, n::Integer) = str^n mymult(str::String, n::Integer) = str^n
mymult(n::Integer, str::AbstractString) = mymult(str, n) mymult(n::Integer, str::String) = mymult(str, n)
``` ```
%% Cell type:markdown id:c89e940f tags: %% Cell type:markdown id:430aba46-e00f-4cef-9edb-062a9f8c5d77 tags:
In both of these, the syntax `str::AbstractString` and `n::Integer` means that the respective method is only In both of these, the syntax `str::String` and `n::Integer` means that the respective method is only
considered during dispatch if the argument `str` is of type `AbstractString` or one of its concrete subtypes and similarly `n` is an `Integer` (or subtype). Since Julia always dispatches to the most specific method in case multiple methods match, this is all we need to do: considered during dispatch if the argument `str` is of type `String` and similarly `n` is an `Integer` (or subtype). Since Julia always dispatches to the most specific method in case multiple methods match, this is all we need to do:
%% Cell type:code id:5fb0009b tags: %% Cell type:code id:5fb0009b tags:
``` julia ``` julia
mymult(2, " abc") mymult(2, " abc")
``` ```
%% Cell type:code id:ee10ad21 tags: %% Cell type:code id:ee10ad21 tags:
``` julia ``` julia
@which mymult(2, " abc") @which mymult(2, " abc")
``` ```
%% Cell type:code id:91192540 tags: %% Cell type:code id:91192540 tags:
``` julia ``` julia
@which mymult("def ", UInt16(3)) @which mymult("def ", UInt16(3))
``` ```
%% Cell type:markdown id:4390819c tags: %% Cell type:markdown id:75814310-5d55-4f7b-8beb-903cb31366df tags:
Notice, that the fully generic Notice, that the fully generic
```julia ```julia
mymult(x, y) = x * y mymult(x, y) = x * y
``` ```
is actually an abbreviation for is actually an abbreviation for
```julia ```julia
mymult(x::Any, y::Any) = x * y mymult(x::Any, y::Any) = x * y
``` ```
%% Cell type:markdown id:eb674aa1-2380-4ae5-b7b2-ad7579239def tags:
### Methods
In Julia different version of a function, which work on different kinds of arguments are called **methods**. You can see the list of methods by running this:
%% Cell type:code id:e52b3aee-2cbc-4cec-b131-204f9889b939 tags:
``` julia
methods(mymult)
```
%% Cell type:markdown id:c72d032a-1041-4dbd-9742-b2fa4c0ae86a tags:
If you run methods on a core function like `*`, you will get quite a long list.
%% Cell type:code id:de3e4302 tags: %% Cell type:code id:de3e4302 tags:
``` julia ``` julia
methods(*) methods(*)
``` ```
%% Cell type:markdown id:b0f1b3da-885d-47b0-83b5-13039d88320e tags:
Using the macro `@which` can be used to discover which specific method is being run.
%% Cell type:code id:be365ee3 tags: %% Cell type:code id:be365ee3 tags:
``` julia ``` julia
@which "Hello"*"World!" println(@which "Hello"*"World!")
println(@which mymult("a", 3))
``` ```
%% Cell type:markdown id:f1c0a83e tags: %% Cell type:markdown id:49e0a478-3718-48bd-8370-029530b23a3f tags:
We can also add some method for `Animal`, `Dog`, and `Cat`: We can also add some method for `Animal`, `Dog`, and `Cat`:
%% Cell type:code id:bfb965d6 tags: %% Cell type:code id:bfb965d6 tags:
``` julia ``` julia
# Define a generic function speak # Define a generic function speak
speak(animal::Animal) = "Some generic animal noise" speak(animal::Animal) = "Some generic animal noise"
# Use multiple dispatch to define method for specific types # Use multiple dispatch to define method for specific types
speak(animal::Dog) = "Woof! I am $(animal.name), a dog." speak(animal::Dog) = "Woof! I am $(animal.name), a dog."
speak(animal::Cat) = "Meow! I am $(animal.name), a cat." speak(animal::Cat) = "Meow! I am $(animal.name), a cat."
``` ```
%% Cell type:markdown id:7d6da0ec tags: %% Cell type:markdown id:ebeeb1e3-f7c0-4a0a-a3fa-539973ef0b36 tags:
Then, let's test it. Then, let's test it.
%% Cell type:code id:14bda70c tags: %% Cell type:code id:14bda70c tags:
``` julia ``` julia
@show speak(a_dog) @show speak(a_dog)
@show speak(a_cat); @show speak(a_cat);
``` ```
%% Cell type:markdown id:85f3e064 tags: %% Cell type:markdown id:790fc10a-4459-4b52-95f6-9718a31d4152 tags:
### Abstract types and multiple dispatch
To show the role of abstract types in multiple dispatch, we go back to our food example. First we need to clarify the hierarchy of food types. We can use abstract types for that.
%% Cell type:code id:bd602d8f-8074-4da6-9b05-1b9b091b7e43 tags:
``` julia
abstract type Food end
abstract type Vegetables <: Food end
struct Potatoes <: Vegetables end
struct Carrots <: Vegetables end
abstract type Fish <: FI will not be able to add him until monday as I don't have access to my notebookood end
struct Salmon <: Fish end
struct Shrimps <: Fish end # not biologically a fish, but we can consider them so from a culinary point of view
```
%% Cell type:code id:3a9a5ca1-d549-49d9-8a91-6c9c58374efb tags:
``` julia
print_type_tree(Food)
```
%% Cell type:markdown id:508debf1-7ac1-44b3-b994-7ddc122dded8 tags:
Now, we found a frying recipe which works decently for any kind of vegetable. Then we will write something like:
%% Cell type:code id:c6e81e79-7d93-40bb-9e18-3c307c4ca8ea tags:
``` julia
function frying(vegetable::Vegetables)
# make tempura
# fry
end
```
%% Cell type:markdown id:8b67deab-9430-4eb1-968d-497065fca06e tags:
If we want to fry our vegetables, we will run
%% Cell type:code id:c9b3b8b9-91ba-4d30-8e71-dca2a478d4df tags:
``` julia
potatoes = Potatoes()
carrots = Carrots()
println(@which frying(potatoes))
println(@which frying(carrots))
```
%% Cell type:markdown id:7071213e-cc38-4fb2-a854-c5ebe0084a46 tags:
But now we found an even more specific recipe that works even better for potatoes. What we will do is writing a new function which is specific for potatoes.
%% Cell type:code id:87cc4c94-1dc6-4515-8b23-135f7428ba1d tags:
``` julia
function frying(potatoes::Potatoes)
# directly fry in oil
end
```
%% Cell type:code id:c4a8bcc4-c551-47d2-ab9f-bdf8e42142c4 tags:
``` julia
println(@which frying(potatoes))
```
%% Cell type:markdown id:db13beea-e3cc-4c6e-b996-d3f6c77fb171 tags:
This example really shows the power of Julia. Multiple dispatch is good for these reasons:
- **Flexibilty:** it is possible to try out new things in a very fast way. We implement a new data type and we use the methods which are already there for abstract types.
- **Customization:** implementing custom behavior for our data types is easy, we simply need to add a custom method.
- **Efficiency:** we can tune the specific methods to be super fast with our specific data type.
%% Cell type:markdown id:b39955b1-eed2-4c1f-bb61-fdcc0554a7b7 tags:
### Exercise ### Exercise
Add some methods for `Animal`, `Dog` and `Cat` and other concrete type from the last exercise. Add some methods for `Animal`, `Dog` and `Cat` and other concrete type from the last exercise.
%% Cell type:code id:6a45fa48 tags: %% Cell type:code id:6a45fa48 tags:
``` julia ``` julia
# TODO: implement your code here. # TODO: implement your code here.
``` ```
%% Cell type:markdown id:ccf57f28 tags:
## Dynamical typing and type deduction
%% Cell type:markdown id:85870a09 tags:
In programming language theory type systems traditionally fall in two categories.
In **dynamically typed** languages the type of a value or expression is inferred only at runtime,
which usually allows for more flexible code. Examples are Python or MATLAB. In contrast, so-called **statically-typed** languages (think FORTRAN or C++), require types to be already known before runtime when the program is compiled. This allows both to check more thoroughly for errors (which can manifest in mismatched types) and it usually brings a gain in performance because more things about the memory layout of the program is known at compile time. As a result, aspects such as vectorization, contiguous alignment of data, preallocation of memory can be leveraged more easily.
Julia is kind of both.
%% Cell type:code id:3ef4e3d0 tags:
``` julia
x = Dog("Buddy", 4) # x is an Dog
@show typeof(x)
x = Cat("Kitty", 3) # Now x is a Cat
@show typeof(x);
```
%% Cell type:markdown id:a02c0a37 tags:
Julia's strong emphasis on types is one of the reasons for its performance and flexibility.
When the code is precompiled before execution, the compiler has the information about the type of all the variables in the program. It will then search the best possible method for each of those. If a specific and highly efficient one is found, that will be used. If a specific one is missing, it will use the next possibility in the type tree, which will still work even if not as efficiently.
**Note:** this look up is the reason why it is not possible to instantiate abstract types, having variables of abstract types would make this operation unnecessary complicated. Moreover, it also reflects reality: a generic vegetable does not physically exist, we only have potatoes, carrots, eggplants and so on.
%% Cell type:markdown id:27812692 tags:
### Three more facts about Julia types:
- In Julia all types are the same. For example, there is no difference between `Int32` and `String`, even though the first has a direct mapping to low-level instructions in the CPU and the latter has not (contrast this with e.g. C++).
- The `Nothing` type with the single instance `nothing` is the Julia equivalent to `void` in C++ or `None` in Python. It often represents that a function does not return anything or that a variable has not been initialised.
- `Any` is the root of the type tree: Any type in Julia is a subtype of `Any`.
%% Cell type:markdown id:ebc902af tags:
##### For more details
https://docs.julialang.org/en/v1/manual/types/
%% Cell type:markdown id:3e8c5b51-3ef6-4d40-9803-dcc2ab6eda3f tags:
## Exercises
%% Cell type:markdown id:bd273bc8-96ea-4260-93db-4934b7291965 tags:
Implement a new abstract type `Polynomial` which is a subtype of `Number`.
TO BE FINISHED
%% Cell type:code id:be3f23e1-b0f8-498f-afd2-9618cb64febb tags:
``` julia
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment