{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Functions\n",
"\n",
"Topics:\n",
"1. How to declare a function\n",
"2. Duck-typing in Julia\n",
"3. Mutating vs. non-mutating functions\n",
"4. Some higher order functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## How to declare a function\n",
"Julia gives us a few different ways to write a function. The first requires the `function` and `end` keywords"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"sayhi (generic function with 1 method)"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"function sayhi(name)\n",
" println(\"Hi $name, it's great to see you!\")\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"f (generic function with 1 method)"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"function f(x)\n",
" x^2\n",
"end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can call either of these functions like this:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hi C-3PO, it's great to see you!\n"
]
}
],
"source": [
"sayhi(\"C-3PO\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1764"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively, we could have declared either of these functions in a single line"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"sayhi2 (generic function with 1 method)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sayhi2(name) = println(\"Hi $name, it's great to see you!\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"f2 (generic function with 1 method)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f2(x) = x^2"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hi R2D2, it's great to see you!\n"
]
}
],
"source": [
"sayhi2(\"R2D2\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1764"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f2(42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we could have declared these as \"anonymous\" functions"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#3 (generic function with 1 method)"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sayhi3 = name -> println(\"Hi $name, it's great to see you!\")"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#5 (generic function with 1 method)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f3 = x -> x^2"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hi Chewbacca, it's great to see you!\n"
]
}
],
"source": [
"sayhi3(\"Chewbacca\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1764"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f3(42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Duck-typing in Julia\n",
"*\"If it quacks like a duck, it's a duck.\"*
\n",
"Julia functions will just work on whatever inputs make sense.
\n",
"For example, `sayhi` works on the name of this minor tv character, written as an integer..."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hi 55595472, it's great to see you!\n"
]
}
],
"source": [
"sayhi(55595472)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And `f` will work on a matrix."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Float64,2}:\n",
" 0.45823 0.530831 0.865254\n",
" 0.863196 0.306726 0.323981\n",
" 0.290885 0.0747215 0.876512"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A = rand(3, 3)\n",
"A"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Float64,2}:\n",
" 0.919874 0.470715 1.32687\n",
" 0.754548 0.5765 1.13023\n",
" 0.452755 0.242824 1.04417"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`f` will also work on a string like \"hi\" because `*` is defined for string inputs as string concatenation."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"hihi\""
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(\"hi\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"On the other hand, `f` will not work on a vector. Unlike `A^2`, which is well-defined, the meaning of `v^2` for a vector, `v`, is not a well-defined algebraic operation."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Float64,1}:\n",
" 0.732826039211119\n",
" 0.02275937266179806\n",
" 0.6160238359606638"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v = rand(3)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"ename": "MethodError",
"evalue": "MethodError: no method matching ^(::Array{Float64,1}, ::Int64)\nClosest candidates are:\n ^(!Matched::Float16, ::Integer) at math.jl:885\n ^(!Matched::Regex, ::Integer) at regex.jl:712\n ^(!Matched::Missing, ::Integer) at missing.jl:155\n ...",
"output_type": "error",
"traceback": [
"MethodError: no method matching ^(::Array{Float64,1}, ::Int64)\nClosest candidates are:\n ^(!Matched::Float16, ::Integer) at math.jl:885\n ^(!Matched::Regex, ::Integer) at regex.jl:712\n ^(!Matched::Missing, ::Integer) at missing.jl:155\n ...",
"",
"Stacktrace:",
" [1] macro expansion at .\\none:0 [inlined]",
" [2] literal_pow at .\\none:0 [inlined]",
" [3] f(::Array{Float64,1}) at .\\In[2]:2",
" [4] top-level scope at In[18]:1"
]
}
],
"source": [
"f(v)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mutating vs. non-mutating functions\n",
"\n",
"By convention, functions followed by `!` alter their contents and functions lacking `!` do not.\n",
"\n",
"For example, let's look at the difference between `sort` and `sort!`."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 3\n",
" 5\n",
" 2"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v = [3, 5, 2]"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 2\n",
" 3\n",
" 5"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lll=sort(v)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 3\n",
" 5\n",
" 2"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 2\n",
" 3\n",
" 5"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lll"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`sort(v)` returns a sorted array that contains the same elements as `v`, but `v` is left unchanged.
\n",
"\n",
"On the other hand, when we run `sort!(v)`, the contents of v are sorted within the array `v`."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 2\n",
" 3\n",
" 5"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sort!(v)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 2\n",
" 3\n",
" 5"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Some higher order functions\n",
"\n",
"### map\n",
"\n",
"`map` is a \"higher-order\" function in Julia that *takes a function* as one of its input arguments.\n",
"`map` then applies that function to every element of the data structure you pass it. For example, executing\n",
"\n",
"```julia\n",
"map(f, [1, 2, 3])\n",
"```\n",
"will give you an output array where the function `f` has been applied to all elements of `[1, 2, 3]`\n",
"```julia\n",
"[f(1), f(2), f(3)]\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 1\n",
" 4\n",
" 9"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"map(f, [1, 2, 3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we've squared all the elements of the vector `[1, 2, 3]`, rather than squaring the vector `[1, 2, 3]`.\n",
"\n",
"To do this, we could have passed to `map` an anonymous function rather than a named function, such as"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#7 (generic function with 1 method)"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x -> x^3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"via"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 1\n",
" 8\n",
" 27"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"map(x -> x^3, [1, 2, 3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and now we've cubed all the elements of `[1, 2, 3]`!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### broadcast\n",
"\n",
"`broadcast` is another higher-order function like `map`. `broadcast` is a generalization of `map`, so it can do every thing `map` can do and more. The syntax for calling `broadcast` is the same as for calling `map`"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 1\n",
" 4\n",
" 9"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"broadcast(f, [1, 2, 3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and again, we've applied `f` (squared) to all the elements of `[1, 2, 3]` - this time by \"broadcasting\" `f`!\n",
"\n",
"Some syntactic sugar for calling `broadcast` is to place a `.` between the name of the function you want to `broadcast` and its input arguments. For example,\n",
"\n",
"```julia\n",
"broadcast(f, [1, 2, 3])\n",
"```\n",
"is the same as\n",
"```julia\n",
"f.([1, 2, 3])\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3-element Array{Int64,1}:\n",
" 1\n",
" 4\n",
" 9"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.([1, 2, 3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice again how different this is from calling\n",
"```julia\n",
"f([1, 2, 3])\n",
"```\n",
"We can square every element of a vector, but we can't square a vector!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To drive home the point, let's look at the difference between\n",
"\n",
"```julia\n",
"f(A)\n",
"```\n",
"and\n",
"```julia\n",
"f.(A)\n",
"```\n",
"for a matrix `A`:"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Int64,2}:\n",
" 1 2 3\n",
" 4 5 6\n",
" 7 8 9"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A = [i + 3*j for j in 0:2, i in 1:3]"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Int64,2}:\n",
" 30 36 42\n",
" 66 81 96\n",
" 102 126 150"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As before we see that for a matrix, `A`,\n",
"```\n",
"f(A) = A^2 = A * A\n",
"```\n",
"\n",
"On the other hand,"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Int64,2}:\n",
" 1 4 9\n",
" 16 25 36\n",
" 49 64 81"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"B = f.(A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"contains the squares of all the entries of `A`.\n",
"\n",
"This dot syntax for broadcasting allows us to write relatively complex compound elementwise expressions in a way that looks natural/closer to mathematical notation. For example, we can write"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Float64,2}:\n",
" 3.0 6.0 9.0\n",
" 12.0 15.0 18.0\n",
" 21.0 24.0 27.0"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A .+ 2 .* f.(A) ./ A"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"instead of"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Float64,2}:\n",
" 3.0 6.0 9.0\n",
" 12.0 15.0 18.0\n",
" 21.0 24.0 27.0"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"broadcast(x -> x + 2 * f(x) / x, A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and the two will perform exactly the same."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercises\n",
"\n",
"#### 6.1\n",
"Write a function `add_one` that adds 1 to its input."
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"add_one (generic function with 1 method)"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"add_one(x) = x +1"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [],
"source": [
"@assert add_one(1) == 2"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [],
"source": [
"@assert add_one(11) == 12"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 6.2\n",
"Use `map` or `broadcast` to increment every element of matrix `A` by `1` and assign it to a variable `A1`."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Int64,2}:\n",
" 2 3 4\n",
" 5 6 7\n",
" 8 9 10"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A1 = add_one.(A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 6.3\n",
"Use the broadcast dot syntax to increment every element of matrix `A1` by `1` and store it in variable `A2`"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3×3 Array{Int64,2}:\n",
" 3 4 5\n",
" 6 7 8\n",
" 9 10 11"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A2 = add_one.(A1)"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [],
"source": [
"@assert A2 == [3 4 5; 6 7 8;9 10 11]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Julia 1.4",
"language": "julia",
"name": "julia-1.3"
},
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
"version": "1.4.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}