{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Function _by Any Other Name_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "```{note}\n", "Functions, especially very long ones, can make your script quite difficult to navigate. For this reason, it is a good practice to keep as many funcions as possible in a separate file.\n", "Running code from external files can be done by running `include(\"procedures.jl\"),` where `procedures.jl` is the name of the file.\n", "You can change your folder by using `cd(...)`\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Functions by `function`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions in `Julia` can be defined in several ways.\n", "One and the most typical is simply by using `function`.\n", "The structure for using `function` is as follows: \n", "\n", "```\n", " function function_name(argument_1, argument_2)\n", " \n", " some_operations\n", " output = some_operations\n", "\n", " return output\n", " end\n", "```\n", "\n", "An example of defining a very simple functions and its execution:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "add_numbers (generic function with 1 method)" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function add_numbers(x, y)\n", " output = x + y\n", " return output\n", "end" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_numbers(5, 12) = 17\n" ] }, { "data": { "text/plain": [ "17" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@show add_numbers(5, 12)" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "w = add_numbers(10, 10) = 20\n" ] }, { "data": { "text/plain": [ "20" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@show w = add_numbers(10,10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions do not need arguments:" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "55" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function add_first_ten_numbers()\n", " Σ = sum( 1:10 )\n", " return Σ\n", "end\n", "\n", "add_first_ten_numbers()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions can return many outpus:" ] }, { "cell_type": "code", "execution_count": 151, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(res1, res2) = add_and_multiply(2, 3) = (5, 6)\n" ] } ], "source": [ "function add_and_multiply(x, y)\n", " out1 = x+y\n", " out2 = x*y\n", "\n", " return out1, out2\n", " \n", "end\n", "\n", "\n", "@show res1, res2 = add_and_multiply(2,3);" ] }, { "cell_type": "code", "execution_count": 152, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 152, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res1" ] }, { "cell_type": "code", "execution_count": 153, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6" ] }, "execution_count": 153, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### \"Assignment-form\" functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is a compact alternative for `function`: `f(args) = some operations`.\n", "Example below:\n" ] }, { "cell_type": "code", "execution_count": 136, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f(2, 1, 3) = 1.6666666666666667\n" ] }, { "data": { "text/plain": [ "1.6666666666666667" ] }, "execution_count": 136, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(x,y,z) = (x^2 + y)/z\n", "@show f(2,1,3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions in this form can contain multiple lines by using `begin`/`end` block:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "g (generic function with 1 method)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "g(x) = begin \n", " y = x^2\n", " y += 1\n", " return(y)\n", " end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(anonymous)=\n", "### Anonymous functions\n", "\n", "Sometimes we do not need to create named functions.\n", "Anonymous functions allow to create a normal function without names.\n", "Their structure is `arg -> operations` with one argument or `(arg1, arg2, arg3) -> operations` with many arguments:" ] }, { "cell_type": "code", "execution_count": 137, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "#17 (generic function with 1 method)" ] }, "execution_count": 137, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x -> x^2\n", "(x,y) -> x+y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can evaluate functions at some values in the following way:" ] }, { "cell_type": "code", "execution_count": 139, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 139, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(x -> x^2)(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`(x -> x^2)` is the function and `3` is the argument of this function.\n", "\n", "Just like assignment-form functions, anonymous functions can be defined within multiple lines using `begin`/`end` blocks:" ] }, { "cell_type": "code", "execution_count": 141, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "#25 (generic function with 1 method)" ] }, "execution_count": 141, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x -> begin\n", " x = x + 1\n", " x = 2*x\n", " return(x)\n", " end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `map` and anonymous functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recall our example from [here](at_dot). We can use anonymous functions as the argument of `map` function to compute $\\frac{{x_i}^2-1}{3}$ for each element of vector $\\mathbf{x}:$" ] }, { "cell_type": "code", "execution_count": 142, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5-element Vector{Float64}:\n", " 0.0\n", " 1.0\n", " 2.6666666666666665\n", " 5.0\n", " 8.0" ] }, "execution_count": 142, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1:5;\n", "\n", "map(xᵢ -> (xᵢ^2-1)/3, x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Function `map(f, c)` transform elements of `c` by applying `f` (which can be an anonymous but also a named function) to each element. \n", "In most cases this function provides very similar results to `broadcast` discussed before. \n", "In my own workflow when I work with anonymous functions I use `map`, while I use `broadcast` only in the dot (`.`) convention.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`map` also works with several lists and multi-argument anonymous functions:" ] }, { "cell_type": "code", "execution_count": 170, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 27\n", " 64" ] }, "execution_count": 170, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map((x,y) -> x^y, \n", " 1:4, #array with x elements\n", " [2 2 3 3] #array with y elements\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scopes: minor digression\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Objects defined outside the function, by default, are not available and their values cannot be changed." ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "UndefVarError: σ not defined", "output_type": "error", "traceback": [ "UndefVarError: σ not defined", "", "Stacktrace:", " [1] modify_σ()", " @ Main ./In[90]:4", " [2] top-level scope", " @ In[90]:8", " [3] eval", " @ ./boot.jl:360 [inlined]", " [4] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)", " @ Base ./loading.jl:1094" ] } ], "source": [ "σ = 12\n", "\n", "function modify_σ()\n", " σ = σ + 1;\n", " return(σ)\n", "end\n", "\n", "modify_σ()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes, if we have a _good_ reason, we may want to change objects created outside the function and not passed as arguments.\n", "To do it we need to point which objects were created beyond the scope of the function. \n", "This can be done with the use of `global`:" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "σ = 12\n", "\n", "function modify_σ()\n", " global σ\n", " σ = σ + 1;\n", " return(σ)\n", "end\n", "\n", "modify_σ()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not only does this function access `σ` but also changes its value in the global scope:" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "σ = 13\n" ] }, { "data": { "text/plain": [ "13" ] }, "execution_count": 94, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@show σ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is convenient to use dictionaries as arguments of functions.\n", "Dictionaries passed as the arguments can be unpacked using the macro `@unpack`, which we already discussed [here](unpack).\n", "This way, inside the function we can write a clean code without cluttering our global space.\n", "Supposed that we want to have a function that evaluates the function at $\\mathbb{x} $ with parameters _twice as large_ as the input arguments $(a, b, c)$.\n", "\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 126, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Dict{String, Any} with 4 entries:\n", " \"c\" => π\n", " \"x\" => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 … 10.0, 10.1, 10.2…\n", " \"b\" => 12\n", " \"a\" => 1" ] }, "execution_count": 126, "metadata": {}, "output_type": "execute_result" } ], "source": [ "equation = Dict([\n", " (\"x\" , collect(range(1, step=.1, length=100)) ),\n", " (\"a\", 1),\n", " (\"b\", 12),\n", " (\"c\", π)\n", " ])" ] }, { "cell_type": "code", "execution_count": 164, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "multiply_coefficients (generic function with 1 method)" ] }, "execution_count": 164, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using Parameters\n", "\n", "function multiply_coefficients(X) \n", " @unpack a, b, c = X;\n", "\n", " a *= 2;\n", " b *= 2;\n", " c *= 2;\n", " \n", "\n", " output = deepcopy(X)\n", " \n", " #instead of writing:\n", " #output[\"y\"] = @. X[\"a\"]*X[\"x\"]^2 + X[\"b\"]*X[\"x\"] + X[\"c\"]\n", " #we have:\n", " \n", " output[\"y\"] = @. a*x^2 + b*x + c \n", "\n", " output[\"a\"] = a;\n", " output[\"b\"] = b;\n", " output[\"c\"] = c;\n", "\n", " \n", "\n", " return output\n", " \n", "end" ] }, { "cell_type": "code", "execution_count": 165, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Dict{String, Any} with 5 entries:\n", " \"c\" => 6.28319\n", " \"x\" => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 … 10.0, 10.1, 10.2…\n", " \"b\" => 24\n", " \"a\" => 2\n", " \"y\" => 62.2832" ] }, "execution_count": 165, "metadata": {}, "output_type": "execute_result" } ], "source": [ "new_result = multiply_coefficients(equation)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result of executing function is assigned to `new_result`.\n", "Inside function `multiply_coefficients`, we were referring to `a`, `b`, `c` normally.\n", "However, after function execution unpacked arguments are still unavailable in the global scope:" ] }, { "cell_type": "code", "execution_count": 129, "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "UndefVarError: a not defined", "output_type": "error", "traceback": [ "UndefVarError: a not defined", "", "Stacktrace:", " [1] top-level scope", " @ show.jl:955", " [2] eval", " @ ./boot.jl:360 [inlined]", " [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)", " @ Base ./loading.jl:1094" ] } ], "source": [ "@show a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Caveat on passing arrays and scalars in functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Consider the following code:" ] }, { "cell_type": "code", "execution_count": 162, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 2\n", "y = [1 2; 3 4]\n", "add_1(x, y) = (1, [1 1; 1 1])\n", "x = 2\n", "y = [1 1; 1 1]\n" ] } ], "source": [ "\n", "using LinearAlgebra\n", "\n", "x = 2\n", "\n", "y = [1 2;3 4]\n", "\n", "set_to_1(scal, matrix)= begin\n", " scal = 1;\n", " matrix .= 1;\n", " return scal, matrix \n", " end\n", "\n", "@show x \n", "@show y \n", "\n", "@show set_to_1(x, y)\n", "\n", "@show x\n", "@show y;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Function `set_to_1` did not modify scalar `x` but modified array `y`.\n", "It is because of how the assignment operator works in `Julia`.\n", "Notice that `matrix .= 1` has an assignment operator `=`, which is applied to an array.\n", "This mean that each element _allocated in the memory_ pointed by `matrix` has a new value. \n", "To understand it better recall [our earlier discussion](assign) on the assignment operator with arrays (and whole dictionaries).\n", "The result of this operation preserves after the execution of the function is finished.\n", "\n", "\n", "```{note}\n", "The behavior of the assignment operator inside functions is quite different from other languages. There is some logic behind it but you have to remember it, especially if you are used to other languages as well.\n", "```\n", "\n", "\n", "One way to write the function without changing the input array is as follows:" ] }, { "cell_type": "code", "execution_count": 163, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 2\n", "y = [1 2; 3 4]\n", "add_1(x, y) = (1, [1 1; 1 1])\n", "x = 2\n", "y = [1 2; 3 4]\n" ] } ], "source": [ "using LinearAlgebra\n", "\n", "x = 2\n", "\n", "y = [1 2;3 4]\n", "\n", "set_to_1(scal, matrix)= begin\n", " scal = 1;\n", " matrix_op = deepcopy(matrix) #new line\n", " matrix_op .= 1; #we use `matrix_op` instead of `matrix`\n", " return scal, matrix_op \n", " end\n", "\n", "@show x \n", "@show y \n", "\n", "@show set_to_1(x, y)\n", "\n", "@show x\n", "@show y;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multi dispatch" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "to be written" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Fun1 (generic function with 2 methods)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "function Fun1(x::Float64,y::Float64)\n", " return(x-y) \n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Fun1 (generic function with 3 methods)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "function Fun1(x::Array{Float64},y::Array{Float64})\n", " return(x .- y) \n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2-element Vector{Int64}:\n", " -2\n", " 0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Fun1([1; 2], [3; 2])" ] }, { "cell_type": "code", "execution_count": 144, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 144, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Fun1(3,2)" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.6.0", "language": "julia", "name": "julia-1.6" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.6.0" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }