Skip to content
Snippets Groups Projects

Exercise 3

1 file
+ 208
77
Compare changes
  • Side-by-side
  • Inline
%% 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:7128c447 tags:
## Abstract and concrete types
%% Cell type:markdown id:723212c2 tags:
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**.
- **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: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:
- `<:`: The subtype operator used to check if a type is a subtype of another type.
- `isabstracttype(T)`: Check if T is an abstract type.
- `isconcretetype(T)`: Check if T is a concrete type.
- `subtypes(T)`: Get a list of all immediate subtypes of type T.
- `supertype(T)`: Get the direct supertype of type T.
%% Cell type:markdown id:00533545 tags:
Abstract types such as `Integer` or `Number` are supertypes of a bunch of other types, for example:
%% Cell type:code id:50b00765 tags:
``` julia
@show Int32 <: Integer # Read Int32 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 <: Number # Float32 is a sub-type of Number
@show Integer <: Number; # Integer is a sub-type of Nummber
```
%% Cell type:code id:329380e7 tags:
``` julia
# by transitivity:
@show Int32 <: Number
@show UInt16 <: Number
@show Number <: Number;
```
%% Cell type:markdown id:aa54bfef tags:
### Type properties
We can check type properties in various ways:
%% Cell type:code id:49228132 tags:
``` julia
@show isconcretetype(Int32);
```
%% Cell type:code id:9ffd1775 tags:
``` julia
@show isabstracttype(Integer);
```
%% Cell type:markdown id:a778662d tags:
A fancy way is even to display a type tree:
%% Cell type:code id:f5a1fd53 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:41f4de49 tags:
``` julia
print_type_tree(Number)
```
%% Cell type:markdown id:c3722ed0 tags:
**Exercise**: Write code to determine all subtypes of `Number`, whether they are abstract or concrete.
%% Cell type:code id:39b8339f tags:
``` julia
# TODO: implement your code
```
%% Cell type:markdown id:dacc835e tags:
To get the super type of a type, one can use `supertype`:
%% Cell type:code id:e666712d tags:
``` julia
@show supertype(Int64)
@show supertype(Signed)
@show supertype(Float16)
@show supertype(Float32)
@show supertype(Bool);
```
%% Cell type:code id:147db9b0 tags:
``` julia
function print_supertypes(T::Type)
println("Supertypes of $T:")
while T != Any
print(T, " ---> ")
T = supertype(T)
end
println(T) # Print Any, which is the ultimate supertype of all types
end
```
%% Cell type:code id:ed566f15 tags:
``` julia
print_supertypes(Int64);
```
%% Cell type:code id:1462bc0c tags:
``` julia
print_supertypes(Float64);
```
%% Cell type:markdown id:808b7968 tags:
### Custom Abstract And Concrete Types
%% Cell type:markdown id:8cbad09b tags:
One can define custom abstract type by using `abstract type`:
%% Cell type:code id:3ebf46f6 tags:
``` julia
abstract type Animal end # Abstract type
```
%% Cell type:markdown id:c1cceba1 tags:
Then, some concrete types can be created as well:
%% Cell type:code id:eaab065e tags:
``` julia
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
```
%% Cell type:markdown id:747d5555 tags:
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.
%% Cell type:code id:b8b7fca8 tags:
``` julia
subtypes(Animal)
```
%% Cell type:markdown id:466cfcf7 tags:
Again, using `isabstracttype` and `isconcretetype` we have
%% Cell type:code id:3eced325 tags:
``` julia
@show isabstracttype(Animal)
@show isabstracttype(Dog)
@show isabstracttype(Cat)
@show isconcretetype(Animal)
@show isconcretetype(Dog)
@show isconcretetype(Cat);
```
%% Cell type:markdown id:580bf73c tags:
The type tree of `Animal` is:
%% Cell type:code id:40debabd tags:
``` julia
print_type_tree(Animal)
```
%% Cell type:markdown id:f4fb75fe tags:
Now, we create two instances from the concrete types:
%% Cell type:code id:6f0f092f tags:
``` julia
a_dog = Dog("Buddy", 3)
a_cat = Cat("Kitty", 2)
@show a_dog a_cat;
```
%% 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:
%% Cell type:code id:263461b1 tags:
``` julia
@show isa(a_dog, Dog)
@show isa(a_dog, Animal)
@show isa(a_dog, Cat)
@show isa(a_cat, Cat);
```
%% Cell type:markdown id:cbb0a853 tags:
The method `typeof` is used to obtain the type of an instance:
%% Cell type:code id:208aed02 tags:
``` julia
@show typeof(a_dog)
@show typeof(a_cat);
```
%% Cell type:markdown id:1be97baf tags:
We can also get all supertypes of `Dog` and `Cat`:
%% Cell type:code id:b3ca83e6 tags:
``` julia
print_supertypes(Dog)
```
%% Cell type:code id:662a732f tags:
``` julia
print_supertypes(Cat)
```
%% Cell type:markdown id:ff7efe19 tags:
### Exercise
Write code to implement some abstract and concrete subtypes of `Animal`, and use `print_type_tree` to view the type tree.
### Exercises
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:
``` julia
# TODO: implement your code
```
%% Cell type:markdown id:9efaf879 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:
%% Cell type:markdown id:52b60c4e-1d3f-46f6-87a9-c3ab2917d8d0 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 vectorisation, contiguous alignment of data,
preallocation of memory can be leveraged more easily.
Which of the following type are subtypes of another?
Try to guess first and then verify by using the operator `<:`.
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
x = Dog("Buddy", 4) # x is an Dog
@show typeof(x)
x = Cat("Kitty", 3) # Now x is a Cat
@show typeof(x);
# TODO: implement your code
```
%% 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.
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.
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).
This might sound a bit unusal at first, but the result is,
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:20a01368-ff73-4992-afae-792962aee09f tags:
%% Cell type:markdown id:27812692 tags:
## Multiple dispatch
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`.
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.
%% 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
Which of the following type are subtypes of another?
Try to guess first and then verify by using the operator `<:`.
- 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.
```julia
Float64 AbstractFloat Integer
Number AbstractArray Complex
Real Any Nothing
```
%% Cell type:markdown id:ebc902af tags:
- 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:
``` python
class potatoes()
potatoes.bake()
potatoes.fry()
```
##### 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:
``` julia
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:
%% Cell type:code id:65dbe501 tags:
``` julia
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:
%% Cell type:code id:8c18999b tags:
``` julia
"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:
%% Cell type:code id:fa0af502 tags:
``` julia
mymult(str::AbstractString, n::Integer) = str^n
mymult(n::Integer, str::AbstractString) = mymult(str, n)
mymult(str::String, n::Integer) = 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
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:
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 `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:
``` julia
mymult(2, " abc")
```
%% Cell type:code id:ee10ad21 tags:
``` julia
@which mymult(2, " abc")
```
%% Cell type:code id:91192540 tags:
``` julia
@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
```julia
mymult(x, y) = x * y
```
is actually an abbreviation for
```julia
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:
``` julia
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:
``` 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`:
%% Cell type:code id:bfb965d6 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."
```
%% Cell type:markdown id:7d6da0ec tags:
%% Cell type:markdown id:ebeeb1e3-f7c0-4a0a-a3fa-539973ef0b36 tags:
Then, let's test it.
%% Cell type:code id:14bda70c tags:
``` julia
@show speak(a_dog)
@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
Add some methods for `Animal`, `Dog` and `Cat` and other concrete type from the last exercise.
%% Cell type:code id:6a45fa48 tags:
``` julia
# 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
```
Loading