5  Function

See Function

5.1 Declare a Function

function sayhi(name)
    println("Hi $name, it's great to see you!")
end
#> sayhi (generic function with 1 method)
sayhi("😸")
#> Hi 😸, it's great to see you!

Single line

square(x) = x^2
#> square (generic function with 1 method)

square(3)
#> 9

5.2 Return Keyword

Like R the last line in the body of function is returned.

If you want to return prematurely use return keyword.

function g(x,y)
    return x * y
    x + y
end
#> g (generic function with 1 method)

g(1, 2)
#> 2

If function has no return value (called for side-effect), return nothing by convention.

5.3 Side Effect !

Function that modifys argument in-place are suffix with !.

function add_one!(V)
    for i in 1:length(V)
        V[i] += 1
    end
    return nothing
end
#> add_one! (generic function with 1 method)
X = [i for i ∈ 1:3]
#> 3-element Vector{Int64}:
#>  1
#>  2
#>  3
add_one!(X)
X
#> 3-element Vector{Int64}:
#>  2
#>  3
#>  4

5.4 Infix Function

1 + 2 + 3
#> 6

+(1,2,3)
#> 6

5.5 Argument-type Declaration

Integer method

fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2)
#> fib (generic function with 1 method)
fib(4)
#> 3

String method

I declare x as a super-type AbstractString to include all possible string type.

fib(x::AbstractString) = x^fib(length(x))
#> fib (generic function with 2 methods)

Now fib has 2 methods

methods(fib)
#> # 2 methods for generic function "fib":
#> [1] fib(n::Integer) in Main at none:3
#> [2] fib(x::AbstractString) in Main at none:3
fib("abcd")
#> "abcdabcdabcd"

Input non-defined argument type will error.

fib(1.5)
# This will error

5.6 Anonymous Function

x -> x^2 + 2x - 1
#> #45 (generic function with 1 method)

The primary use for anonymous functions is passing them to functions which take other functions as arguments.

Useful in map(f, collection)

map(x -> x^2, 1:3)
#> 3-element Vector{Int64}:
#>  1
#>  4
#>  9

5.7 Functional Programming

5.7.1 Map

📖 = [1.2, 2.7]
#> 2-element Vector{Float64}:
#>  1.2
#>  2.7
map(round, 📖)
#> 2-element Vector{Float64}:
#>  1.0
#>  3.0
map(x -> 2x + 1, 📖)
#> 2-element Vector{Float64}:
#>  3.4
#>  6.4

5.7.2 Broadcast

Shorter way is to broadcast a function.

h(x) = 2x + 1
#> h (generic function with 1 method)
h.(📖)
#> 2-element Vector{Float64}:
#>  3.4
#>  6.4

5.8 Multiple Return Values

A tuple is useful for return multiple values from a function.

function foo(a,b)
    (add = a+b, prod = a*b)
end
#> foo (generic function with 1 method)
x = foo(2,3)
#> (add = 5, prod = 6)

x
#> (add = 5, prod = 6)
x.add
#> 5

5.9 Destructuring Assignment

A comma-separated list of variables (optionally wrapped in parentheses) can appear on the left side of an assignment: the value on the right side is destructured by iterating over and assigning to each variable in turn:

(a,b,c) = 1:3 
#> 1:3
# or 
a, b, c = 1:3
#> 1:3

b
#> 2

Destructuring assignment extracts each value from function into a variable:

a, b = foo(4, 5)
#> (add = 9, prod = 20)

a
#> 9
b
#> 20

Underscore _

If only a subset of the elements of the iterator are required, a common convention is to assign ignored elements to a variable consisting of only underscores _

_, _, _, d = 1:10
#> 1:10

d
#> 4

slurping ...

If the last symbol in the assignment list is suffixed by ... (known as slurping), then it will be assigned a collection or lazy iterator of the remaining elements of the right-hand side iterator:

a, b... = "hello"
#> "hello"

a
#> 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
b
#> "ello"
_, a, b... = 1:5
#> 1:5

a
#> 2
b
#> 3-element Vector{Int64}:
#>  3
#>  4
#>  5

5.10 Argument destructuring

If a function argument name is written as a tuple (e.g. (x, y)) instead of just a symbol, then an assignment (x, y) = argument will be inserted for you:

gap((min, max)) = max - min
#> gap (generic function with 1 method)
gap((1, 3))
#> 2
minmax(x, y) = (y < x) ? (y, x) : (x, y)
#> minmax (generic function with 1 method)
minmax(3, 2)
#> (2, 3)
minmax(3, 2) |> gap
#> 1

Notice the |> pipe operator.

For anonymous functions, destructuring a single tuple requires an extra comma:

map( ((x,y),) -> x + y, [(1,2), (3,4)] )
#> 2-element Vector{Int64}:
#>  3
#>  7

5.11 Do Block

From this map(long_f, collection)

map(x->begin
           if x < 0 && iseven(x)
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end,
    [-1, 0, 1])
#> 3-element Vector{Int64}:
#>  -1
#>   1
#>   1

Is equivalent to map(collection) do long_f

map([-1, 0, 1]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end
#> 3-element Vector{Int64}:
#>  -1
#>   1
#>   1

The do x syntax creates an anonymous function with argument x and passes it as the first argument to map.

Do blog might help reader to read from left to right.

5.12 Compose & Piping

(f ∘ g)(args...) is the same as f(g(args...))

composition operator is ∘ \circ<tab>

sqrt(sum([1, 3]))
#> 2.0

becomes

(sqrt ∘ sum)([1, 3])
#> 2.0

Pipe

1:10 |> sum |> sqrt
#> 7.416198487095663

Vectorized Pipe

["a", "list", "of", "strings"] .|> uppercase
#> 4-element Vector{String}:
#>  "A"
#>  "LIST"
#>  "OF"
#>  "STRINGS"