3 + 2
+(3, 2)
5
Marie-Hélène Burle
Functions are objects containing a set of instructions.
When you pass a tuple of argument(s) (possibly an empty tuple) to them, you get one or more values as output.
Operators are functions and can be written in a way that shows the tuple of arguments more explicitly.
For instance, you can use the addition operator (+
) in 2 ways:
The multiplication operator can be omitted when this does not create any ambiguity:
Julia has “assignment by operation” operators:
There is a left division operator:
Julia supports fraction operations:
There are 2 ways to define a new function:
function <name>(<arguments>)
<body>
end
Example:
<name>(<arguments>) = <body>
Example:
The function hello1
defined with this terse syntax is exactly the same as the one we defined above.
Julia suggests to use lower case without underscores as function names when the name is readable enough.
Since you pass a tuple to a function when you run it, you call a function by appending parentheses to its name:
Here, our function does not take any argument, so the tuple is empty.
Our function hello1
does not accept any argument. If we pass an argument, we get an error message:
LoadError: MethodError: no method matching hello1(::String)
To define a function which accepts an argument, we need to add a placeholder for it in the function definition.
So let’s try this:
Mmm … not quite … this function works but does not give the result we wanted.
Here, we need to use string interpolation:
$name
in the body of the function points to name
in the tuple of argument.
When we run the function, $name
is replaced by the value we used in lieu of name
in the function definition:
Here is the corresponding assignment form for hello3
:
Note that this dollar sign is only required with strings. Here is an example with integers:
And the corresponding assignment form:
Now, let’s write a function which accepts 2 arguments. For this, we put 2 placeholders in the tuple passed to the function in the function definition:
hello4 (generic function with 1 method)
This means that this function expects a tuple of 2 values:
Your turn:
See what happens when you pass no argument, a single argument, or three arguments to this function.
You can set a default value for some or all arguments. In this case, the function will run with or without a value passed for those arguments. If no value is given, the default is used. If a value is given, it will replace the default.
Example:
Another example:
In Julia, functions return the value(s) of the last expression automatically.
If you want to return something else instead, you need to use the return
statement. This causes the function to exit early.
Look at these 5 functions:
function test1(x, y)
x + y
end
function test2(x, y)
return x + y
end
function test3(x, y)
x * y
x + y
end
function test4(x, y)
return x * y
x + y
end
function test5(x, y)
return x * y
return x + y
end
function test6(x, y)
x * y, x + y
end
Your turn:
Without running the code, try to guess the outputs of:
Your turn:
Now, run the code and draw some conclusions on the behaviour of the return statement.
Anonymous functions are functions which aren’t given a name:
function (<arguments>)
<body>
end
In compact form:
<arguments> -> <body>
Example:
Compact form:
This is very useful for functional programming (when you apply a function—for instance map
—to other functions to apply them in a vectorized manner which avoids repetitions).
Example:
|>
is the pipe in Julia.
It redirects the output of the expression on the left as the input of the expression on the right.
The following 2 expressions are equivalent:
Here is another example:
You can pass a function inside another function:
<function2>(<function1>(<arguments>))
<arguments>
will be passed to <function1>
and the result will then be passed to <function2>
.
An equivalent syntax is to use the composition operator ∘
(in the REPL, type \circ
then press tab):
(<function2> ∘ <function1>)(<arguments>)
Example:
Your turn:
Write three other equivalent expressions using the pipe.
Another example:
Your turn:
Try to write the same expression in another 2 different ways.
Functions usually do not modify their argument(s):
Julia has a set of functions which modify their argument(s). By convention, their names end with !
The function sort has a mutating equivalent sort!:
If you write functions which modify their arguments, make sure to follow this convention too.
To apply a function to each element of a collection rather than to the collection as a whole, Julia uses broadcasting.
Let’s create a collection (here a tuple):
If we pass a
to the string function, that function applies to the whole collection:
In contrast, we can broadcast the function string to all elements of a:
An alternative syntax is to add a period after the function name:
Here is another example:
ERROR: MethodError: no method matching abs(::Array{Int64,1})
This doesn’t work because the function abs
only applies to single elements.
By broadcasting abs
, you apply it to each element of a
:
The dot notation is equivalent:
It can also be applied to the pipe, to unary and binary operators, etc.
Example:
Your turn:
Try to understand the difference between the following 2 expressions:
In some programming languages, functions can be polymorphic (multiple versions exist under the same function name). The process of selecting which version to use is called dispatch.
There are multiple types of dispatch depending on the language:
This is typical of object-oriented languages such as Python, C++, Java, Smalltalk, etc.
This the case of Lisp and Julia. In Julia, these versions are called methods.
Running methods(+)
let’s you see that the function +
has 206 methods!
Methods can be added to existing functions.