{ "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 }