Skip to content

Commit

Permalink
Smoother transition between control flow and function episode (#66)
Browse files Browse the repository at this point in the history
* show methods output

* update functions chapter

* rework interface section

* split off loops

* move keypoints

* headers

* add random search

* finish loop

* rename files

* write code to code folder

* change back

* update Revise path

* improve paths

* fix generate

* auto update code
  • Loading branch information
BeastyBlacksmith authored Sep 11, 2023
1 parent 60c537d commit 86ab17e
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 172 deletions.
1 change: 1 addition & 0 deletions .github/workflows/update-episodes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
run: |
ls -RF
mv output/carpentries/*.md episodes
mv output/code/* code
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
Expand Down
93 changes: 59 additions & 34 deletions 05_Write_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ Trebuchets.shoot(5, 0.25pi, 500)[2]

function shoot_distance(windspeed, angle, weight)
Trebuchets.shoot(windspeed, angle, weight)[2]
#md end ;
#nb end
end

# !!! note "Implicit return"
# Note that Melissa didn't have to use the `return` keyword, since in Julia the
Expand All @@ -161,8 +160,7 @@ shoot_distance(5, 0.25pi, 500)

function shoot_distance(trebuchet::Trebuchet, env::Environment)
shoot_distance(env.wind, trebuchet.release_angle, trebuchet.counterweight)
#md end ;
#nb end
end

# This method will call the former method and pass the correct fields from the
# `Trebuchet` and `Environment` structures.
Expand All @@ -176,8 +174,7 @@ function shoot_distance(trebuchet::Trebuchet, env::Environment)

function shoot_distance(args...) # slurping
Trebuchets.shoot(args...)[2] # splatting
#md end ;
#nb end
end

# ### Anonymous functions

Expand All @@ -186,40 +183,40 @@ function shoot_distance(args...) # slurping
# These are _anonymous functions_.
# They can be defined with either the so-called stabby lambda notation,

(windspeed, angle, weight) -> Trebuchets.shoot(windspeed, angle, weight)[2] ;
(windspeed, angle, weight) -> Trebuchets.shoot(windspeed, angle, weight)[2]

# or in long form, by omitting the name:

function (windspeed, angle, weight)
Trebuchets.shoot(windspeed, angle, weight)[2]
#md end ;
#nb end
end

# ### Errors and macros
# ### Calling methods
#
# Now, that she defined all these methods she tests calling a few

# Melissa would like to set the fields of a `Trebuchet` using an index.
# She writes
shoot_distance(5, 0.25pi, 500)

#md # ```julia
#md # Trebuchets[1] = 2
#md # ```
#

#nb Trebuchets[1] = 2
shoot_distance([5, 0.25pi, 500])

#md # ```error
#md # ERROR: MethodError: no method matching setindex!(::Trebuchet, ::Int64, ::Int64)
#md # Stacktrace:
#md # [1] top-level scope
#md # @ REPL[4]:1
#md # ```
# For the other method she needs to construct `Trebuchet` and `Environment` objects first

env = Environment(5, 100)

#

trebuchet = Trebuchet(500, 0.25pi)

# ### Errors and macros

# The error tells her two things:
# This error tells her two things:

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

# Melissa wants to add the missing method to `setindex!` but she doesn't know
# Melissa wants to add the missing method to `size` but she doesn't know
# where it is defined.
# There is a handy _macro_ named `@which` that obtains the module where the
# function is defined.
Expand All @@ -230,23 +227,51 @@ function (windspeed, angle, weight)
# Macros can transform any valid Julia expression and are quite powerful.
# They can be expanded by prepending `@macroexpand` to the macro call of
# interest.
using InteractiveUtils #hide

#md # ```julia
#md # @which setindex!
#md # ```
@which size

#nb @which setindex!
# Now Melissa knows she needs to add a method to `Base.size` with the
# signature `(::Trebuchet)`.
# She can also lookup the docstring using the `@doc` macro

#md # ```output
#md # Base
#md # ```
@doc size

# With that information she can now implement this method:

Base.size(::Trebuchet) = tuple(2)

# Now she can try again

trebuchet = Trebuchet(500, 0.25pi)

# Again, there is an error but this time the error message is different:
# It's no longer a method for `size` that is missing but for `getindex`.
# She looks up the documentation for that function

@doc getindex

# Note that the documentation for all methods gets shown and Melissa needs to look for the relevant method first.
# In this case its the paragraph starting with
# ````
# getindex(A, inds...)
# ````
# After a bit of pondering the figures it should be enough to add a method for `getindex` with a single number.
# ````
# getindex(trebuchet::Trebuchet, i::Int)
# ````
#
# !!! note "Syntactic sugar"
# In Julia `a[1]` is equivalent to `getindex(a, 1)`
# and `a[2] = 3` to `setindex!(a, 3, 2)`
# Likewise `a.b` is equivalent to `getproperty(a, :b)`
# and `a.b = 4` to `setproperty!(a, :b, 4)`.

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


# !!! keypoints
# - "You can think of functions being a collection of methods"
# - "Methods are defined by their signature"
# - "The signature is defined by the number of arguments, their order and their type"
# - "Keep the number of positional arguments low"
# - "Macros transform Julia expressions"
124 changes: 124 additions & 0 deletions 06_Interfacing_conditions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# !!! yaml
# ---
# title: "Interfaces & conditionals"
# teaching: 30
# exercises: 30
# ---
#
# !!! questions
# - "How to use conditionals?"
# - "What is an interface?"
#
# !!! objectives
#

# ## Conditionals

include("definition.jl")
Base.size(::Trebuchet) = tuple(2)

# Now that Melissa knows that she has to add a method for
# ````
# getindex(trebuchet::Trebuchet, i::Int)
# ````
# she thinks about the implementation.

# If the index is `1` she wants to get the `counterweight` field and if the index is `2`
# she wants to get `release_angle` and since these are the only two 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.getindex(trebuchet::Trebuchet, i::Int)
if i === 1
return trebuchet.counterweight
elseif i === 2
return trebuchet.release_angle
else
error("Trebuchet only accepts indices 1 and 2, yours is $i")
end
end

# And tries again:

trebuchet = Trebuchet(500, 0.25pi)

# Notice, that the printing is different from our `trebuchet` in [the former episode](03_Julia_type_system.ipynb).

# ### Interfaces

# Why is that?
# By subtyping `Trebuchet` as `AbstractVector` we implicitly opted into
# a widespread _interface_ in the Julia
# language: `AbstractArray`s.
# An interface is a collection of methods that should be implemented by all subtypes of the interface type in order for generic code to work.
# For example, the [Julia manual](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array) lists all methods that a subtype of
# `AbstractArray` need to implement to adhere to the `AbstractArray` interface:

# - `size(A)` returns a tuple containing the dimensions of `A`
# - `getindex(A, i::Int)` returns the value associated with index `i`
# - `setindex!(A, v, i::Int)` writes a new value `v` at the index `i` (optional)

# Now, that Melissa implemented the mandatory methods for this interface for the `Trebuchet` type, it will work
# with every function in `Base` that accepts an `AbstractArray`.
# She tries a few things that now work without her writing explicit code for it:

trebuchet + trebuchet

#

using LinearAlgebra
dot(trebuchet, trebuchet)

#

trebuchet * transpose(trebuchet)

# That is, it now behaves like you would expect from an ordinary matrix.

# Now she goes about implementing the missing optional method for `setindex!` of the `AbstractArray` interface.

# !!! freecode "Implement `setindex!`"
#
# Write the missing method for `setindex(trebuchet::Trebuchet, v, i::Int)` similar to Melissas `getindex` function.
#
# !!! solution
#
# ```julia
# 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
# ```

#md function Base.setindex!(trebuchet::Trebuchet, v, i::Int) #hide
#md if i === 1 #hide
#md trebuchet.counterweight = v #hide
#md elseif i === 2 #hide
#md trebuchet.release_angle = v #hide
#md else #hide
#md error("Trebuchet only accepts indices 1 and 2, yours is $i") #hide
#md end #hide
#md end #hide

# With the new `Trebuchet` defined with a complete `AbstractArray` interface,
# Melissa tries her new method to modify a counterweight by index:

trebuchet[1] = 2
#-

trebuchet




# !!! keypoints
# - "Interfaces are informal"
# - "Interfaces facilitate code reuse"
# - "Conditions use `if`, `elseif`, `else` and `end`"
Loading

0 comments on commit 86ab17e

Please sign in to comment.