Defining functions

Julia function definitions syntax is pretty standard:


In [1]:
function doubler(x)
    return 2x  # okay to have literal * variable without *
end


Out[1]:
doubler (generic function with 1 method)

In [2]:
doubler(2), doubler(6)


Out[2]:
(4,12)

But we can also omit some syntax in the interest of making things shorter:


In [3]:
function tripler(x)
    3x  # implicit return
end


Out[3]:
tripler (generic function with 1 method)

In [4]:
tripler(3.5), tripler(2)


Out[4]:
(10.5,6)

In [5]:
halver(x) = x//2


Out[5]:
halver (generic function with 1 method)

In [6]:
halver(5), halver(4)


Out[6]:
(5//2,2//1)

And finally, for the functional crowd:


In [7]:
firstelem = x -> x[1]


Out[7]:
(anonymous function)

In [8]:
firstelem("tester")


Out[8]:
't'

Pass by sharing

In Python, particularly NumPy, we don't default to copies when passing function arguments. Julia works the same way.


In [9]:
A = rand(5, 5)


Out[9]:
5x5 Array{Float64,2}:
 0.339317  0.868068  0.0285906  0.383576  0.88558  
 0.797496  0.033205  0.206466   0.629436  0.883407 
 0.397588  0.479591  0.295639   0.535099  0.0230995
 0.056555  0.538183  0.204241   0.805164  0.999334 
 0.959375  0.397445  0.922842   0.382563  0.904041 

In [10]:
function zerodiag!(x)
    for i in 1:maximum(size(x))  # it's okay (and fast!) to write a loop
        x[i, i] = 0
    end
    return x
end


Out[10]:
zerodiag! (generic function with 1 method)

In [11]:
B = zerodiag!(A)


Out[11]:
5x5 Array{Float64,2}:
 0.0       0.868068  0.0285906  0.383576  0.88558  
 0.797496  0.0       0.206466   0.629436  0.883407 
 0.397588  0.479591  0.0        0.535099  0.0230995
 0.056555  0.538183  0.204241   0.0       0.999334 
 0.959375  0.397445  0.922842   0.382563  0.0      

In [12]:
B, A


Out[12]:
(
5x5 Array{Float64,2}:
 0.0       0.868068  0.0285906  0.383576  0.88558  
 0.797496  0.0       0.206466   0.629436  0.883407 
 0.397588  0.479591  0.0        0.535099  0.0230995
 0.056555  0.538183  0.204241   0.0       0.999334 
 0.959375  0.397445  0.922842   0.382563  0.0      ,

5x5 Array{Float64,2}:
 0.0       0.868068  0.0285906  0.383576  0.88558  
 0.797496  0.0       0.206466   0.629436  0.883407 
 0.397588  0.479591  0.0        0.535099  0.0230995
 0.056555  0.538183  0.204241   0.0       0.999334 
 0.959375  0.397445  0.922842   0.382563  0.0      )

Splatting and slurping (varargs)

It can be useful to package multiple inputs to a function into an interable (cf. *args).


In [13]:
function parser(var, val, rest...) 
    println("variable name: $var")
    println("variable value: $val")
    println(rest)
end


Out[13]:
parser (generic function with 1 method)

In [14]:
parser(:x, 5, 7, 8, "foo")


variable name: x
variable value: 5
(7,8,"foo")

Conversely, we'd like to be able to "splat" a collection into separate arguments for passing to a function:


In [15]:
function randlike(A)
    sizetup = size(A)
    return rand(sizetup...)
end


Out[15]:
randlike (generic function with 1 method)

In [16]:
randlike(ones(5))


Out[16]:
5-element Array{Float64,1}:
 0.239124
 0.946497
 0.315101
 0.481288
 0.422785

Defaults and keyword arguments

Julia supports two common use cases we know from Python: defaults and keyword arguments. These are a little less flexible than in Python but allow for effective compilation.


In [17]:
function Lpnorm(x, p=2)
    return sum(x.^p)^(1/p)
end


Out[17]:
Lpnorm (generic function with 2 methods)

Note two methods! We'll come back to this.


In [18]:
aa = rand(10)
norm(aa), Lpnorm(aa), Lpnorm(aa, 1)


Out[18]:
(1.447976030767568,1.447976030767568,3.6510042638287827)

We can also use keywords:


In [19]:
function makecircle(x, y, radius=1; color="black", fill=color)  # later kwargs can use values of previous
    println("$color circle of radius $radius centered at ($x, $y) with $fill border")
end


Out[19]:
makecircle (generic function with 2 methods)

In [20]:
makecircle(0, 0)


black circle of radius 1 centered at (0, 0) with black border

In [21]:
makecircle(0, 0, 5)


black circle of radius 5 centered at (0, 0) with black border

In [22]:
makecircle(0, 0, color="blue")  # no semicolon needed


blue circle of radius 1 centered at (0, 0) with blue border
makecircle(0, 0, 5, "blue", "green") # error: kwargs are keyword *only*

As we'll see, Julia is less flexible about keyword vs. positional arguments because this is required to keep multiple dispatch sane.

Multimethods, dispatch (teaser)

Each case above could basically work in Python. In fact, we can do something very like duck typing:


In [23]:
lastelem(x) = x[end]


Out[23]:
lastelem (generic function with 1 method)

In [24]:
lastelem(1:5), lastelem([2, 4, 6]), lastelem("foobar")


Out[24]:
(5,6,'r')

But what if we needed some special casing:

doubler("foo") # will fail

We'd like to be able to solve a few problems:

  • let doubler handle multiple cases, depending on input
  • extend doubler if it's not our function

In Python and other object-oriented languages, this is solved with classes. Classes dispatch on instances (i.e., based on self). In Julia, we'll solve this problem with multiple dispatch.

First, however, we need to understand how types work in Julia.


In [ ]: