7 Important Julia Methods To Extend
Integrate your new project into Julian syntax by dispatching these vital methods.

The Julia language is a very interesting one, not just for its speed and application, but also in paradigm. Julia uses multiple dispatch as a paradigm, bringing the relatively rudimentary implementations of the concept in other languages full circle and applying it to everything. One of the coolest things in that regard is the ability to add new methods to a given function using multiple dispatch. This helps phenomenally to make the language far more extensible than most other programming languages. I recently discussed this topic in detail in another article, which if you are interested in you may read here:
The best part is, this is not even the only technique one can use to extend Julia. Additionally, extending functions feels very natural because it is very much based on the things that one might typically do in Julia. Extending functions is as simple as writing functions, and that is pretty awesome.
When it comes to extending methods, there are many in Julia’s Base that are frequently extended in order to provide new functionality. Today I wanted to cover some common functions to extend for your type, and how to write new methods for that type to create some awesome functionality.
Before we get into extending methods for our type, let us quickly create a type to be used with the examples. For this, I am making a simple Person constructor with a few different fields:
mutable struct Person
name::String
age::Int64
class::Symbol
endWe also want to make sure we directly import all of the Base fields we want to extend:
import Base: getindex, setindex!, display, show, vect, iterate, +, (:)
import Base: push!If you would like to dive into this code and reference it for yourself, here is a link to a notebook with all of the code for this article inside of it:
№1: getindex
The first, and likely most obvious, method that you might want to extend is the getindex method. This method comes directly from Base and is called each time that we use [] on a given type to access elements. Calling this method with a Vector and an Integer as an argument, for example, will yield the element contained at the index of our Integer .
julia> getindex([5, 10], 2)
10This is equivalent to
julia> [5, 10][2]
10We could dispatch this to Person, however indexing for person really could not do much as it is not really a collection. However, we can also make an appropriate dispatch for multiple Persons inside of a Vector by dispatching Vector{Person} .
typeof([Person("emmy", 22, :history)])Vector{Person} (alias for Array{Person, 1})Let us say that we wanted to be able to index by name. We would add this functionality by dispatching getindex to our Vector{Person} type with a String, like so:
getindex(v::Vector{Person}, name::String) = v[findall(person -> person.name == name, v)[1]]In order to get this information, I use the findall method from Base. The method of this function takes a function and a vector as positional arguments. The return is a Vector of Integers, which contains each index where the function returned true. In this case, the function returns true if the the Person’s name is equal to the provided name. Finally, we index the Vector{Person} by the first element of the Vector{Int64}, returning the value stored at the first found person.
people = [Person("emmy", 22, :history), Person("john", 25, :math)]people["emmy"]
Person("emmy", 22, :history)Because this is all done with multiple dispatch, we could also add more indexing rules for different types, such as the Symbol that represents our class:
function getindex(v::Vector{Person}, class::Symbol)
pos::Vector{Int64} = findall(person -> person.class == class, v)
[v[p] for p in pos]::Vector{Person}
endpeople[:history]1-element Vector{Person}:
Person("emmy", 22, :history)№2: setindex!
The accompaniment to the getindex function is the setindex! function. Given that this function has an exclamation point at the end, we know that this function mutates our type. Of course, this makes a lot of sense for setting an index as we are probably mutating elements using this method. For this, I am going to make it so that we can change a person’s class by indexing a name and setting it to a Symbol. One thing I find odd is that the value we are setting is the center argument, not the rightmost — just thought this was an interesting note to make, as I have written it wrong many times due to the way you think about it in your head. We can recycle our getindex binding from before in order to make this function incredibly simple:
setindex!(v::Vector{Person}, class::Symbol, name::String) = v[name].class = classSetting a name index to a Symbol will now yield a mutation to that Person:
people["emmy"] = :mathpeople[:math]2-element Vector{Person}:
Person("emmy", 22, :math)
Person("john", 25, :math)people[:history]Person[]№3: vect
Another cool method to dispatch to is the vect() method. Dispatching this method will change what happens whenever we vectorize our type and create a Vector{Person}(). In this case, just as an example, we want all of our names to be unique. We can easily create this by making a check to see if each name is unique whenever Base.vect is called, and then promptly returning a Vector as the method is meant to do.
function vect(p::Person ...)
names::Vector{String} = [person.name for person in p]
if length(Set(names))!= length(names) throw(ArgumentError("names not unique")) end
Vector{Person}([person for person in p])::Vector{Person}
endI added this functionality by checking if a Set of the names’ length is equal to the length of the names themselves. A Set is just a list of each unique value inside of a given Vector, so if the names are not the same length then there must be a duplicate name. If we provide the names correctly, we get exactly what is intended from before:
people = [Person(“emmy”, 22, :history), Person(“john”, 25, :math)]
2-element Vector{Person}:
Person("emmy", 22, :history)
Person("john", 25, :math)However, providing emmy twice now throws an ArgumentError:
people = [Person("emmy", 22, :history), Person("emmy", 25, :math)]
ArgumentError: names not unique№4: display (and show)
The default showing of most constructed types will yield all of the different fields along with the type name printed in their outer constructor form. This is some pretty ugly output, in my subjective view. Fortunately, we can change this sort of output by binding our type to display. We can also bind our display to different mimes in order to make the way we show our type change depending on the circumstances. I actually wrote an entire article, which will go more into detail on display; as well as show, which you may read here:
function display(p::Person)
display("text/markdown", """### $(p.name)
This person is in $(p.class) class, and is $(p.age) years old.""")
endNow calling the display method on our type will display the little markdown summary we just created:
display(people["emmy"])
In order to make this show by default for the person, we will need to bind this to show:
show(io::IO, p::Person) = display(p)To make this a bit cooler, let us also bind show to a Vector{Person}, as well:
show(io::IO, p::Vector{Person}) = [display(person) for person in p]; return№5: iterate
For our application, we really do not need to bind iterate, but in this example I am going to make iteration take place specifically on the names. Of course, this does not really make sense in most instances, if we wanted to iterate the names, then we would simply make a new method that creates a Vector of each name and call that each time we iterate. It makes a lot more sense to avoid dispatching iterate when possible. However, for today’s example I want to demonstrate just that! If you want to extend this method, the documentation actually gives a great write-up on exactly how to do so:
?(iterate)“””
iterate(iter [, state]) -> Union{Nothing, Tuple{Any, Any}}
Advance the iterator to obtain the next element. If no elements remain, nothing should be returned. Otherwise, a 2-tuple of the next element and the new iteration state should be returned.
“””
Pay close attention to the input and outputs. Our iterate method will be receiving both the iterable, as well as the current iteration. It is our job to then provide an adequate return. We also need to determine when the iteration is done, and if this is the case, we need to return nothing. If we do not do this, then our iterator will continue going forever. With each step, we return the current loop variable and the step (advanced by one) inside of a 2-element tuple.
function iterate(x::Vector{Person}, state::Int64)
if state > length(x)
nothing
else
x[state].name, state + 1
end
endNow iterating a Vector{Person} will yield the Person’s name on each iteration:
for p in people
println(p)
end
emmy
john№6: push!
One of the most commonly used methods in Julia when working with collections is push!. The push! method is used to add new elements to an iterable. I have an entire article that discusses push! in more detail which might be helpful if this method is new to you:
Extending push! is a lot more simple than iterate, and in most cases we will not need to do this at all. However, as we wanted to check the names when calling Base.vect, we might also want to do this here, so that is the application I will be binding push! to for this example.
function push!(v::Vector{Person}, p::Person)
names::Vector{String} = [person.name for person in p]
push!(names, p.name)
if length(Set(names))!= length(names) throw(ArgumentError("names not unique")) end
append!(v, p)
endI originally tried this, and it was actually quite funny because I was confused at first why I was getting String does not have field name , but this occurred because we binded iterate before, which is another great reason not to do that. Anyway, here is that comprehension modified in order to simply grab the name itself:
function push!(v::Vector{Person}, p::Person)
names::Vector{String} = [person for person in v]
push!(names, p.name)
if length(Set(names))!= length(names) throw(ArgumentError("names not unique")) end
append!(v, p)
endPushing myself into the people array yields:
push!(people, Person("emmy", 22, :math))ArgumentError: names not unique№7: Base operators
The final thing that it might be important to extend in Julia is the Base operators. This is primarily useful if you are working with numerical values. Of course, this is not really the case here, but we could still make use of operators for concatenation and grouping. In this case, however, I am simply going to be binding + to add people’s ages together. As to what application that would actually be used for, I have no idea.
+(p1::Person, p2::Person) = p1.age + p2.agepeople[1] + people[2]47
I also have an entire article on extending Julia’s Base articles (I have a lot of articles about Julia,) which can certainly provide more information on this topic. If interested, here is a link:
In terms of extensibility, Julia really is a language that has yet to be matched by any other. This is shown very clearly by the examples that can be made using Julia’s Base. Compared to a lot of the other techniques that are commonly used in other languages to provide this kind of functionality, I think that multiple dispatch does this very elegantly! Thank you so much for reading my article, it really does mean the world to me! I hope that this article brought some methods that were previously unknown to your attention, as that was my intention! One of these that I actually did not know about until recently is Base.vect, which I am thankful to have learned!





