Methods in Julia
Broadcasting, methods and multiple-dispatch
This is part 9 of the Julia Tutorials (a series of tutorials on Julia). If you are not familiar with Julia functions, please go through part 8 for better understanding.
If you are not familiar with Julia and want to get started with it, feel free to check out the complete series @ —
Note: Use Julia’s REPL to quickly follow along with the codes in this tutorial
Broadcasting
Broadcasting in Julia helps to write performant code. Think of an application where you want to update an iterable such as an array element-wise, the most straight forward approach is iterating through each entry of the array using a for
loop and updating them. But, this can be made more efficient by using Julia’s built-in broadcasting operators. We can apply broadcasting on both Julia’s built-in and user defined functions. Broadcasting can be done in 2 ways:
Using broadcast
keyword
function increment(a, factor=1)
a += factor
endarr = [1, 2, 3, 4, 5, 6, 7]# Broadcast the increment function over the array
arr = broadcast(increment, arr)
In the above example, the increment
function updates the value of the argument a
by factor
. So when increment
is broadcasted over the array arr
, each element of arr
gets incremented by factor
.
If you want to broadcast over only a certain arguments, it can be done like this:
arr = broadcast(i -> increment(i, 2), arr)
Using .
(vectorized dot) operator
If you have gone through part 6 of this tutorial series (Operators in Julia), you would probably remember about the vectorized dot operator. Let’s see how we can broadcast our increment
function over arr
using .
operator.
arr = increment.(arr)
For broadcasting over only certain arguments, wrap the arguments that shouldn’t be broadcasted inside Ref
arr = increment.(Ref(2), arr)
Methods and Multiple-Dispatch
When same kind of operation needs to be done for different argument types, we can define the same function with different number and types of arguments. i.e, in the case of adding 2 numbers, we can implement the same addition logic in 2 different functions (with same name and different argument types), one for floating point addition and another for integer addition.
Julia provides such a functionality by which different implementations of the same concept can be implemented easily. Each of these implementations can have different argument type combinations and different behaviors associated with them. The definition of one of these behaviors is termed as a method.
In Julia, each time a function is called, the most appropriate method is applied based on it’s arguments. Applying a method when calling a function is known as dispatch. Object-oriented languages traditionally only consider the first argument in dispatch. Julia is different in that it takes all the arguments of the function into account (not just the first) and then chooses which method to call. This is known as multiple dispatch.
Multiple dispatch is used by almost all built-in functions and operators in Julia. For example, Julia’s *
perform different operations for different types, for numeric types, it involves multiplication, while for strings, it means string concatenation.
# with numeric types
3 * 2 # Yields 6
3.5 * 2 # Yields 7# with string types
"Good" * " morning" # Yields "Good morning"
To make use of multiple dispatch in our program, we can construct our own methods for a function. Creating a method is pretty much similar to creating a function.
Let’s implement different methods for a function to add it’s arguments.
# Method 1
add(x::Number, y::Number) = x + y# Method 2
function add(x::String, y::String)
println("Method 2 invoked")
return "$x $y"
end# Method 3
function add(x, y::String)
println("Method 3 invoked")
return "$x$y"
end# Method 4
function add(x::String, y)
println("Method 4 invoked")
return "$x-$y"
end
In the above example, we have declared 4 methods where:
- Method 1: Adds arguments
x
andy
if they are of typeNumber
- Method 2: Merge arguments separated by a white space if they are of type
String
- Method 3: Merge arguments if the 1st argument is of type
Any
and the 2nd one aString
- Method 4: Merge arguments separated by a
-
if the 1st argument is of typeString
and the 2nd one isAny
Julia provides methods
keyword which returns all the methods constructed for a function. In our example, if we call methods
on add
, we will get the following:
# 4 methods for generic function "add":
[1] add(x::String, y::String) in Main at REPL[2]:2
[2] add(x::Number, y::Number) in Main at REPL[1]:1
[3] add(x, y::String) in Main at REPL[3]:2
[4] add(x::String, y) in Main at REPL[4]:2
Now, let’s see how Julia uses multiple dispatch to call our method of best fit.
add(1, 2) # Invokes Method 1
add("Good", "morning") # Invokes Method 2
add(4, "ever") # Invokes Method 3
add("method", 4) # Invokes Method 4
As you can see, When a function is called, Julia finds the best possible method defined for that function based on all the argument types (and not just the first argument) and invokes the most specialized method available.