Skip to content

Commit

Permalink
Add package episode, finish modules (#11)
Browse files Browse the repository at this point in the history
* finish section about modules

* add section about Revise

* finish package section

* delete 'd'

* fix types challenge

* fix juliahub link

* fix more challenges

* missing "

* add if/else section

* don't use type parameters

* add meta information

* Update _episodes/07-modules.md

Co-authored-by: Mosè Giordano <[email protected]>

Co-authored-by: Mosè Giordano <[email protected]>
  • Loading branch information
BeastyBlacksmith and giordano authored Jun 2, 2021
1 parent f01557e commit aef6805
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .vscode/carpentries.snippet.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"> ## $1",
"> > ## ${2:Solution}",
">{: .solution}",
"{: .challange}",
"{: .challenge}",
"$0"
]
}
Expand Down
6 changes: 2 additions & 4 deletions _episodes/02-REPL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Using the REPL"
teaching: 20
exercises: 0
teaching: 18
exercises: 2
questions:
- "How to use the REPL?"
objectives:
Expand All @@ -13,8 +13,6 @@ keypoints:
- "Pressing <kbd>?</kbd> enters help mode."
- "Pressing <kbd>;</kbd> enters shell mode."
- "Pressing <kbd>]</kbd> enters pkg mode."
- "Functions are compiled when first called with a given set of arguments and cached for later reuse."
- "Redefinition of functions is possible with the Revise package."

---

Expand Down
22 changes: 12 additions & 10 deletions _episodes/03-types.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
---
title: "Julia type system"
teaching: 60
exercises: 0
teaching: 58
exercises: 2
questions:
- "What is the use of types?"
- "How are types organized in julia?"
objectives:
- "Understand the structure of the type tree."
- "Know how to traverse the type tree."
- "Know how to build abstract, mutable and immutable types."
- "Know how to use type parameters."
- "Know how to build mutable and immutable types."
keypoints:
- "Types enable the compiler to optimize code."
- "In julia types have only one direct supertype."
- "Types increase security, Type annotations are mainly used for dispatch."
- "Parametric types are useful for flexible yet performant code."
---

## Structuring variables
Expand Down Expand Up @@ -91,19 +87,25 @@ julia> subtypes(Real)
> > ## Solution
> > Since `Float64` is a subtype of `Real` `1.0` is also a `Real`.
>{: .solution}
{: .challange}
{: .challenge}


## Creating a subtype

A concrete type can be made a subtype of an abstract type with the subtype operator `<:`.
Since `Trebuchet` contains several fields that are mutable Melissa thinks it is a good idea to make it a subtype of `AbstractArray`.
Since `Trebuchet` contains several fields that are mutable Melissa thinks it is a good idea to make it a subtype of `AbstractVector`.
~~~
mutable struct Trebuchet <: AbstractArray
mutable struct Trebuchet <: AbstractVector{Float64}
counterweight::Float64
release_angle::Float64
end
~~~
{: .language-julia}

> ## Caveat: redefining `struct`s
> In julia it is not very easy to redefine `struct`s.
> It is necessary to restart the REPL to define the new definition of `Trebuchet`
> or take a different name.
{: .callout}

{% include links.md %}
17 changes: 11 additions & 6 deletions _episodes/04-pkg.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
---
title: "Using the package manager"
teaching: 30
teaching: 20
exercises: 0
questions:
-
- "Where do I find packages?"
- "How do I add pacakges?"
- "How can I use packages?"
objectives:
-
- "Learn to add packages using pkg-mode"
- "Learn to resolve name conflicts"
- "Learn to activate environments"
keypoints:
-
-
- "Find pacakges on juliahub"
- "add packages using `pkg> add`"
- "use many small environments rather than one big environment"
---

## The package manager

Now it is time for Melissa and their mates to simulate the launch of the trebuchet.
The necessary equations are really complicated, but an investigation on https://juliahub.com/ revealed that someone already implemented these and published it as the julia package `Trebuchet.jl`.
The necessary equations are really complicated, but an investigation on [juliahub](https://juliahub.com/) revealed that someone already implemented these and published it as the julia package `Trebuchet.jl`.
That spares some real work.

Melissa enters the package mode by pressing <kbd>]</kbd>.
Expand Down
49 changes: 46 additions & 3 deletions _episodes/05-functions.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
---
title: "Write functions!"
teaching: 30
exercises: 0
teaching: 35
exercises: 5
questions:
- "How do I call a function?"
- "Where can I find help about using a function?"
- "What are methods?"
objectives:
- "usage of positional and keyword arguments"
- "defining named and anonymous functions"
- "reading error messages"
keypoints:
- "You can think of functions being a collection of methods"
- "Keep the number of positional arguments low"
- "Macros transform julia expressions"
---

## Working with functions
Expand Down Expand Up @@ -41,7 +50,7 @@ help?> names
> >## Solution
> >`names(Trebuchets, all = true)`
> {: .solution}
{: .challange}
{: .challenge}


Thus Melissa executes
Expand Down Expand Up @@ -152,4 +161,38 @@ julia> function (windspeed, angle, weight)
~~~
{: .language-julia}

### Errors and macros

Melissa would like to set the fields of a `Trebuchet` using an index.
She writes
~~~
julia> trebuchet[1] = 2
ERROR: MethodError: no method matching setindex!(::Trebuchet{Int64}, ::Int64, ::Int64)
Stacktrace:
[1] top-level scope
@ REPL[4]:1
~~~
{: .language-julia}

which tells her two things:

1. a function named `setindex!` was called
2. it didn't have a method for `Trebuchet`s

Melissa wants to add the missing method to `setindex!` but she doesn't know where it is defined.
There is a handy _macro_ named `@which` which can be used to obtain the module where the function is defined.
~~~
julia> @which setindex!
Base
~~~
{: .language-julia}

> ## Macros
> Macro names begin with `@` and they don't need parentheses or commas to delimit their arguments.
> Macros can transform any valid julia expression and are quite powerful.
> They can be expanded using `@macroexpand`.
{: .callout}

Now Melissa knows she needs to add a method to `Base.setindex!` with the signature `(::Trebuchet{Int64}, ::Int64, ::Int64)`.

{% include links.md %}
29 changes: 26 additions & 3 deletions _episodes/06-control-flow.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
---
title: "Control flow"
teaching: 45
exercises: 0
exercises: 15
questions:
- "What are for and while loops?"
- "How to use conditionals?"
- "What is an interface?"
objectives:
keypoints:
- "Interfaces are informal"
- "Use for loops for a known number of iterations and while loops for an unkown number of iterations."
---

{% include links.md %}

## Conditionals

<!-- TODO: write a section introducin if elseif else -->
Now that Melissa knows which method to add she thinks about the implementation.

If the index is `1` she wants to set `counterweight` while if the index is `2` she wants to set `release_angle` and since these are the only to fields she wants to return an error if anything else comes in.
In julia the keywords to specify conditions are `if`, `elseif` and `else`.
Closed with an `end`.

Thus she writes
~~~
function Base.setindex!(trebuchet::Trebuchet, v, i::Int)
if i === 1
trebuchet.counterweight = v
elseif i === 2
trebuchet.release_angle = v
else
error("Trebuchet only accepts indices 1 and 2, yours is $i")
end
end
~~~
{: .language-julia}

### Interfaces

`setindex!` is actually one function of a widespread _interface_ in the julia language: `AbstractArray`s.
An interface is a collection of methods that are all implemented by a certain type.
For example lists the [julia manual](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array) all methods that a subtype of `AbstractArray` need to implement to adhere to the `AbstractArray` interface.
If Melissa does this then her `Trebuchet` type will work with every function in `Base` that accepts an `AbstractArray`.
Expand All @@ -23,7 +47,6 @@ If Melissa does this then her `Trebuchet` type will work with every function in
> Now we know enough to actually implement the `AbstractArray` interface.
> You don't need to implement the optional methods.
> We set `IndexStyle(Trebuchet) = IndexLinear()`
> <!-- TODO: this might be a bit too difficult -->
>
>> ## Solution
>> ~~~
Expand Down
112 changes: 107 additions & 5 deletions _episodes/07-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ questions:
- "Whats the purpose of modules?"
objectives:
- "Structure your code using modules"
- "Use Revise.jl to track changes"
keypoints:
- "Modules introduce namespaces. Public API has to be documented and can be exported."
- "Modules introduce namespaces"
- "Public API has to be documented and can be exported."
---

## Modules
Expand All @@ -22,11 +24,77 @@ This is what it looks like:
import Trebuchet as Trebuchets
using ForwardDiff: gradient
mutable struct Trebuchet <: AbstractVector{Float64}
counterweight::Float64
release_angle::Float64
end
Base.copy(trebuchet::Trebuchet) = Trebuchet(trebuchet.counterweight, trebuchet.release_angle)
Base.size(trebuchet::Trebuchet) = tuple(2)
Base.getindex(trebuchet::Trebuchet, i::Int) = getfield(trebuchet, i)
function Base.setindex!(trebuchet::Trebuchet, v, i::Int)
if i === 1
trebuchet.counterweight = v
elseif i === 2
trebuchet.release_angle = v
else
error("Trebuchet only accepts indices 1 and 2, yours is $i")
end
end
struct Environment
wind::Float64
target_distance::Float64
end
function shoot_distance(windspeed, angle, weight)
Trebuchets.shoot(windspeed, angle, weight)[2]
end
function shoot_distance(args...)
Trebuchets.shoot(args...)[2]
end
function shoot_distance(trebuchet::Trebuchet, env::Environment)
shoot_distance(env.wind, trebuchet.release_angle, trebuchet.counterweight)
end
function aim(trebuchet::Trebuchet, environment::Environment; ε = 1e-1, η = 0.05)
better_trebuchet = copy(trebuchet)
hit = x -> (shoot_distance([environment.wind, x[2], x[1]]) - environment.target_distance)
while abs(hit(better_trebuchet)) > ε
grad = gradient(hit, better_trebuchet)
better_trebuchet -= η * grad
end
return Trebuchet(better_trebuchet[1], better_trebuchet[2])
end
imprecise_trebuchet = Trebuchet(500.0, 0.25pi)
environment = Environment(5, 100)
precise_trebuchet = aim(imprecise_trebuchet, environment)
shoot_distance(precise_trebuchet, environment)
~~~
{: .language-julia}

Now Melissa can run `include(aim_trebuchet.jl)` in the REPL to execute her code.

She also recognizes that she has a bunch of definitions at the beginning that she doesn't need to execute more than once in a session and some lines at the end that use these definitions which she might run more often.
She will split these in two separate files and put the definitions into a _module_.
The module will put the definitions into their own namespace which is the module name.
This means Melissa would need to put the module name before each definition if she uses it outside of the module.
But she remembers from the [Pkg episode]({{ page.root }}{% link _episodes/04-pkg.md %}) that she can export names that don't need to be prefixed.

She names her module `MelissasModule` and accordingly the file `MelissasModule.jl`.
From this module she exports the names `aim`, `shoot_distance`, `Trebuchet` and `Environment`.
This way she can leave her other code unchanged.
~~~
module MelissasModule
import Trebuchet as Trebuchets
using ForwardDiff: gradient
export aim, shoot_distance, Trebuchet, Environment
mutable struct Trebuchet{T} <: AbstractVector{T}
counterweight::T
release_angle::T
mutable struct Trebuchet <: AbstractVecor{Float64}
counterweight::Float64
release_angle::Float64
end
Base.copy(trebuchet::Trebuchet) = Trebuchet(trebuchet.counterweight, trebuchet.release_angle)
Base.size(trebuchet::Trebuchet) = tuple(2)
Expand Down Expand Up @@ -65,15 +133,49 @@ function aim(trebuchet::Trebuchet, environment::Environment; ε = 1e-1, η = 0.0
end
return Trebuchet(better_trebuchet[1], better_trebuchet[2])
end
end # MelissasModule
~~~
{: .language-julia}

The rest of the code goes to a file she calls `MelissasCode.jl`.
~~~
using .MelissasModule
imprecise_trebuchet = Trebuchet(500.0, 0.25pi)
environment = Environment(5, 100)
precise_trebuchet = aim(imprecise_trebuchet, environment)
shoot_distance(precise_trebuchet, environment)
~~~
{: .language-julia}

Now she can include `MelissasModule.jl` once, and change and include `MelissasCode.jl` as often as she wants.
But what if she wants to make changes to the module?
If she changes the code in the module, reincludes the module and runs her code again, she only gets a bunch of warnings, but her changes are not applied.

## Revise.jl

`Revise.jl` is a pacakge that can keep track of changes in your files and load these in a running julia session.

Melissa needs to take two things into account
- `using Revise` must come before `using` any Package that she wants to be tracked
- she should use `includet` instead of `include` for included files (`t` for "tracking")

Thus she now runs
~~~
julia> using Revise
julia> includet("MelissasModule.jl")
julia> include("MelissasCode.jl")
100.0975848073789
~~~
{: .language-julia}

Now Melissa can run `include(aim_trebuchet.jl)` in the REPL to execute her code.
and any change she makes in `MelissasModule.jl` will be visible in the next run of her code.

> ## Did I say any changes?
> Well, almost any. Revise can't track changes to structures.
{: .callout}

{% include links.md %}
Loading

0 comments on commit aef6805

Please sign in to comment.