Believe it or not but the following is actual Julia code which generates Fibonacci numbers:
Julia 0.6 introduced the new ∘ (\circ) function composition operator. It currcntly has one method:
which has a rather straightforward definition:
It takes functions f
and g
and composes them in the following way:
For example
julia> sin(cos(2))
-0.4042391538522658
julia> (sin∘cos)(2)
-0.4042391538522658
julia> (sin∘cos∘log)(2)
0.6955886362231636
julia> sin(cos(log(2)))
0.6955886362231636
The operator provides a nice way of creating anonymous functions for broadcasting across arrays:
julia> map(reverse∘string, 230:240)
11-element Array{String,1}:
"032"
"132"
"232"
"332"
"432"
"532"
"632"
"732"
"832"
"932"
"042"
Ultimately, the ∘ operator is a great demonstration of Julia’s flexibility as a language. Which leads to the question: What else can we do with Julia’s operators?
For starters, it may be nice to be able to compose functions and arguments so that (arg∘f)(x)
is equivalent to f(arg,x)
. For example, the following does not work:
julia> map(10∘(-), 1:3)
ERROR: MethodError: objects of type Int64 are not callable
Stacktrace:
[1] _collect(::UnitRange{Int64}, ::Base.Generator{UnitRange{Int64},Base.##55#56{Int64,Base.#-}}, ::Base.EltypeUnknown, ::Base.HasShape) at ./array.jl:454
[2] map(::Function, ::UnitRange{Int64}) at ./abstractarray.jl:1865
However, thanks to Julia’s overloading abilities, we can make it work ourselves. Here we define both arg∘f
and f∘arg
:
julia> import Base.∘
julia> (∘)(f::Function, arg) = (x...)->f(x...,arg)
∘ (generic function with 2 methods)
julia> (∘)(arg, f::Function) = (x...)->f(arg,x...)
∘ (generic function with 3 methods)
julia> map(10∘(-), 1:3)
3-element Array{Int64,1}:
9
8
7
julia> map((-)∘10, 1:3)
3-element Array{Int64,1}:
-9
-8
-7
We can now define some simple functions very easily:
julia> my_log2 = 2∘log
(::#7) (generic function with 1 method)
julia> my_log2(10)
3.3219280948873626
julia> log2(10) # Julia built-in
3.321928094887362
This is great but we created an additional problem. Our old code no longer works!
julia> (sin∘cos)(3)
ERROR: MethodError: ∘(::Base.#sin, ::Base.#cos) is ambiguous. Candidates:
∘(arg, f::Function) in Main at REPL[32]:1
∘(f::Function, arg) in Main at REPL[31]:1
Possible fix, define
∘(::Function, ::Function)
Julia doesn’t know which of our functions to call. Since both arg
and f
are of type Function
(typeof(sin) <: Funtion == true
and typeof(cos) <: Function == true
), it doesn’t know which to use as f::Function
and which to use as args::Any
. In fact, we don’t want it to use either, we want it to use the original definition if both arguments are functions. Luckily, this is a very easy fix in Julia. We can define a specialized version of the original definition like so:
Notice that this definition is exactly the same as the one above except that it includes specific types for its arguments f
and g
. Julia is now able to choose the proper function:
Another way we can abuse- ahem demonstrate Julia’s flexibility with operators is to use them to perform common complex operations, for example, here we define the /
operator to run fold
on a single dimensional list:
julia> import Base./
julia> /{T}(l::AbstractArray{T,1}, f::Function) = foldr(f,l)
/ (generic function with 74 methods)
Which can be used in the following way:
Or perhaps we want the ^
operator to repeat a function:
julia> import Base.^
julia> function ^(f::Function, iters::Int)
fn = f
for i in 1:(iters-1)
fn = fn ∘ f
end
fn
end
^ (generic function with 53 methods)
Which allows the following:
Gauss would be proud.
Lets use our new operators to generate Fibonacci numbers. The first few terms of this sequence are the following:
0,1,1,2,3,5,8,13,21,34,55,...
The recursive definition for this sequence is:
Lets say we have an array containing two adjacent numbers in the sequence:
julia> x = [8,13]
In order to generate the next Fibonacci number, we simply add the two items together.
julia> x[1] + x[2]
21
If we want to continue generating the next Fibonacci number, we will also need to save the previous one:
julia> next_x = [x[2], x[1] + x[2]]
[13,21]
Or equivalently, using our /
operator to add l[1]
and l[2]
:
Lets define a function so we can easily generate Fibonacci numbers:
julia> next_fib(x) = [x[2], x/+]
next_fib (generic function with 1 method)
julia> x = next_fib([8,13])
2-element Array{Int64,1}:
13
21
julia> x = next_fib(x)
2-element Array{Int64,1}:
21
34
julia> x = next_fib(x)
2-element Array{Int64,1}:
34
55
We can generate the n
th Fibonacci number if we start at the first two Fibonacci numbers [0,1]
and run the function n-1
times:
julia> function fib(n)
x = [0,1]
for i in 1:n-1
x = next_fib(x)
end
return x[2]
end
fib (generic function with 1 method)
julia> fib(10)
55
Or equivalently, using our ^
operator:
Now we can swap out next_fib
for it’s definition:
And finally, to make the code a little more concise (why not?) we use 0:1
instead of [0,1]
and index the first element of the n
th list instead of the second element of the n-1
th list.