A function is an object, mapping a tuple of arguments using an expression to return a value. When this function object is unable to return a value, it throws an exception. For different types of arguments, the same conceptual function can have different implementations.
For example, we could have a function to add two floating point numbers and another function to add two integers. But, conceptually, we are only adding two numbers. Julia provides a functionality through which different implementations of the same concept can be implemented easily.
The functions don't need to be defined all at once; they are defined in small abstracts. These small abstracts are different argument type combinations and have different behaviors associated with them. The definition of one of these behaviors is called a method. The types and the number of arguments that a method definition accepts is indicated by the annotation of its signatures. Therefore, the most suitable method is applied whenever a function is called with a certain set of arguments.
To apply a method when a function is invoked is known as dispatch. There are two types of dispatch:
- dynamic-based on type evaluated at the run-time type
- multiple-based on all arguments, not just the receiver
Julia chooses which method should be invoked based on all the arguments. This is known as multiple dispatch. Multiple dispatch is particularly useful for mathematical and scientific code. We shouldn't consider the operations as belonging to one argument any more than any of the others. All of the argument types are considered when implementing a mathematical operator. Multiple dispatch is not limited to mathematical expressions, as it can be used in numerous real-world scenarios and is a powerful paradigm for structuring programs. The code is given as follows:
type MyType prop::String end MyType(v::Real) = ... function MyType{T}(v::Vector{T}) # parametric type .... end
The "+" symbol is a function in Julia using multiple dispatch. Multiple dispatch is used by all of Julia's standard functions and operators. For the various possible combinations of argument types and counts, all of them have many methods defining their behavior. A method is restricted to taking certain types of arguments using the ::type-assertion
operator:
julia> f(x::Float64, y::Float64) = x + y
f (generic function with 1 method)
The function definition will only be applied for calls where x
and y
are both values of the Float64
type:
julia> f(10.0, 14.0)
24.0
If we try to apply this definition to other types of arguments, it will give a method error:
julia> f(5,10.0) ERROR: MethodError: no method matching f(::Int64, ::Float64) Closest candidates are: f(::Float64, ::Float64) at REPL[4]:1
The arguments must be of precisely the same type as defined in the function definition. The function object is created in the first method definition. New method definitions add new behaviors to the existing function object. When a function is invoked, the number and types of the arguments are matched, and the most specific method definition matching will be executed. The following example creates a function with two methods. One method definition takes two arguments of the Float64
type and adds them. The second method definition takes two arguments of the Number
type, multiplies them by two and adds them. When we invoke the function with Float64
arguments, the first method definition is applied, and when we invoke the function with integer arguments, the second method definition is applied, as the number can take any numeric values. In the following example, we are playing with floating point numbers and integers using multiple dispatch:
julia> f(x::Number, y::Number) = x + y f (generic function with 2 methods) julia> f(100,200) 300
In Julia, all values are instances of the abstract type Any
. When the type declaration is not given with ::
, that means it is not specifically defined as the type of the argument, therefore Any
is the default type of method parameter and it doesn't have the restriction of taking any type of value. Generally, one method definition is written in such a way that it will be applied to certain arguments to which no other method definition applies. This is one of the Julia language's most powerful features. Specialized code can be generated expressively and efficiently, and complex algorithms can be implemented, without paying much attention to low-level implementations, using Julia's multiple dispatch and flexible parametric type system. We will study multiple dispatch in detail in the coming chapters.