{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "tQ1r1bbb0yBv" }, "source": [ "# _for Pythonistas_\n", "\n", "> TL;DR: _Julia looks and feels a lot like Python, only much faster. It's dynamic, expressive, extensible, with batteries included, in particular for Data Science_.\n", "\n", "This notebook is an **introduction to Julia for Python programmers**.\n", "\n", "It will go through the most important Python features (such as functions, basic types, list comprehensions, exceptions, generators, modules, packages, and so on) and show you how to code them in Julia." ] }, { "cell_type": "markdown", "metadata": { "id": "f_1dr-2W5iSU" }, "source": [ "You can either run this notebook in Google Colab, or using Jupyter on your own machine." ] }, { "cell_type": "markdown", "metadata": { "id": "6ew60wXrZM0W" }, "source": [ "# Setup\n", "\n", "Let's start by installing some libraries needed in this notebook (I'll explain this code later in this notebook). This will take about one minute on Colab." ] }, { "cell_type": "code", "source": [ "using Pkg\n", "\n", "if haskey(ENV, \"COLAB_GPU\") # only install libraries if we are running on Colab\n", " pkg\"add BenchmarkTools; precompile;\" # ~15s\n", " pkg\"add PyCall; precompile;\" # ~40s\n", " pkg\"add PyPlot; precompile;\" # 15s\n", "end\n" ], "metadata": { "id": "o64HqwuK2rdF", "outputId": "16d90ec4-a64b-425c-94d1-45e124050ea3", "colab": { "base_uri": "https://localhost:8080/" } }, "execution_count": 1, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General.toml`\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m BenchmarkTools ─ v1.6.2\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[6e4b80f9] \u001b[39m\u001b[92m+ BenchmarkTools v1.6.2\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[6e4b80f9] \u001b[39m\u001b[92m+ BenchmarkTools v1.6.2\u001b[39m\n", " \u001b[90m[9abbd945] \u001b[39m\u001b[92m+ Profile v1.11.0\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 2549.6 ms\u001b[32m ✓ \u001b[39mBenchmarkTools\n", " 1 dependency successfully precompiled in 10 seconds. 488 already precompiled.\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m PyCall ─ v1.96.4\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Building\u001b[22m\u001b[39m PyCall → `~/.julia/scratchspaces/44cfe95a-1eb2-52ea-b672-e2afdf69b78f/9816a3826b0ebf49ab4926e2b18842ad8b5c8f04/build.log`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 17651.9 ms\u001b[32m ✓ \u001b[39mPyCall\n", " 1 dependency successfully precompiled in 19 seconds. 489 already precompiled.\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m PyPlot ─ v2.11.6\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[d330b81b] \u001b[39m\u001b[92m+ PyPlot v2.11.6\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[d330b81b] \u001b[39m\u001b[92m+ PyPlot v2.11.6\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 2968.0 ms\u001b[32m ✓ \u001b[39mPyPlot\n", " 1 dependency successfully precompiled in 4 seconds. 490 already precompiled.\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ] }, { "cell_type": "markdown", "metadata": { "id": "2ZvetMlVYKXY" }, "source": [ "If you prefer to run this notebook on your machine instead of Google Colab:\n", "\n", "* Download this notebook (File > Download .ipynb)\n", "* Install [Julia](https://julialang.org/downloads/)\n", "* Run the following command in a terminal to install `IJulia` (the Jupyter kernel for Julia), and a few packages we will use:\n", "\n", " ```bash\n", " julia -e 'using Pkg\n", " pkg\"add IJulia; precompile;\"\n", " pkg\"add BenchmarkTools; precompile;\"\n", " pkg\"add PyCall; precompile;\"\n", " pkg\"add PyPlot; precompile;\"'\n", " ```\n", "\n", "* Next, go to the directory containing this notebook:\n", "\n", " ```julia\n", " cd /path/to/notebook/directory\n", " ```\n", "\n", "* Start Jupyter Notebook:\n", "\n", " ```bash\n", " julia -e 'using IJulia; IJulia.notebook()'\n", " ```\n", "\n", " Or replace `notebook()` with `jupyterlab()` if you prefer JupyterLab.\n", "\n", " If you do not already have [Jupyter](https://jupyter.org/install) installed, IJulia will propose to install it. If you agree, it will automatically install a private Miniconda (just for Julia), and install Jupyter and Python inside it.\n", "\n", "* Lastly, open this notebook and skip directly to the next section." ] }, { "cell_type": "markdown", "metadata": { "id": "-OS3Ac017T1i" }, "source": [ "## Checking the Installation\n", "The `versioninfo()` function should print your Julia version and some other info about the system (if you ever ask for help or file an issue about Julia, you should always provide this information)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "EEzvvzCl1i0F", "outputId": "29121d53-0026-41ff-869c-db1c9537ccbb" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Julia Version 1.11.5\n", "Commit 760b2e5b739 (2025-04-14 06:53 UTC)\n", "Build Info:\n", " Official https://julialang.org/ release\n", "Platform Info:\n", " OS: Linux (x86_64-linux-gnu)\n", " CPU: 2 × Intel(R) Xeon(R) CPU @ 2.00GHz\n", " WORD_SIZE: 64\n", " LLVM: libLLVM-16.0.6 (ORCJIT, skylake-avx512)\n", "Threads: 2 default, 0 interactive, 1 GC (on 2 virtual cores)\n", "Environment:\n", " LD_LIBRARY_PATH = /usr/lib64-nvidia\n", " JULIA_NUM_THREADS = auto\n" ] } ], "source": [ "versioninfo()" ] }, { "cell_type": "markdown", "metadata": { "id": "GnSoOX6btmkh" }, "source": [ "# Getting Help\n", "To get help on any module, function, variable, or just about anything else, just type `?` followed by what you're interested in. For example:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 188 }, "id": "-4FMkizktjB5", "outputId": "aac6de68-d63c-4411-b8b2-341c1d1d26b2" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "search: \u001b[0m\u001b[1mv\u001b[22m\u001b[0m\u001b[1me\u001b[22m\u001b[0m\u001b[1mr\u001b[22m\u001b[0m\u001b[1ms\u001b[22m\u001b[0m\u001b[1mi\u001b[22m\u001b[0m\u001b[1mo\u001b[22m\u001b[0m\u001b[1mn\u001b[22m\u001b[0m\u001b[1mi\u001b[22m\u001b[0m\u001b[1mn\u001b[22m\u001b[0m\u001b[1mf\u001b[22m\u001b[0m\u001b[1mo\u001b[22m \u001b[0m\u001b[1mv\u001b[22marinfo\n", "\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "\u001b[36m versioninfo(io::IO=stdout; verbose::Bool=false)\u001b[39m\n", "\n", " Print information about the version of Julia in use. The output is\n", " controlled with boolean keyword arguments:\n", "\n", " • \u001b[36mverbose\u001b[39m: print all additional information\n", "\n", "\u001b[33m\u001b[1m │ \u001b[22m\u001b[39m\u001b[33m\u001b[1mWarning\u001b[22m\u001b[39m\n", "\u001b[33m\u001b[1m │\u001b[22m\u001b[39m\n", "\u001b[33m\u001b[1m │\u001b[22m\u001b[39m The output of this function may contain sensitive information.\n", "\u001b[33m\u001b[1m │\u001b[22m\u001b[39m Before sharing the output, please review the output and remove any\n", "\u001b[33m\u001b[1m │\u001b[22m\u001b[39m data that should not be shared publicly.\n", "\n", " See also: \u001b[36mVERSION\u001b[39m." ], "text/markdown": "```\nversioninfo(io::IO=stdout; verbose::Bool=false)\n```\n\nPrint information about the version of Julia in use. The output is controlled with boolean keyword arguments:\n\n * `verbose`: print all additional information\n\n!!! warning\n The output of this function may contain sensitive information. Before sharing the output, please review the output and remove any data that should not be shared publicly.\n\n\nSee also: [`VERSION`](@ref).\n", "text/latex": "\\begin{verbatim}\nversioninfo(io::IO=stdout; verbose::Bool=false)\n\\end{verbatim}\nPrint information about the version of Julia in use. The output is controlled with boolean keyword arguments:\n\n\\begin{itemize}\n\\item \\texttt{verbose}: print all additional information\n\n\\end{itemize}\n\\begin{quote}\n\\textbf{warning}\n\nWarning\n\nThe output of this function may contain sensitive information. Before sharing the output, please review the output and remove any data that should not be shared publicly.\n\n\\end{quote}\nSee also: \\href{@ref}{\\texttt{VERSION}}.\n\n" }, "metadata": {}, "execution_count": 3 } ], "source": [ "?versioninfo" ] }, { "cell_type": "markdown", "metadata": { "id": "iAZabyzVEKlX" }, "source": [ "This works in interactive mode only: in Jupyter, Colab and in the Julia shell (called the REPL)." ] }, { "cell_type": "markdown", "metadata": { "id": "QvXUXo-XUt3h" }, "source": [ "Here are a few more ways to get help and inspect objects in interactive mode:\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`?obj` | `help(obj)`\n", "|`dump(obj)` | `print(repr(obj))`\n", "|`names(FooModule)` | `dir(foo_module)`\n", "|`methodswith(SomeType)` | `dir(SomeType)`\n", "|`@which func` | `func.__module__`\n", "|`apropos(\"bar\")` | Search for `\"bar\"` in docstrings of all installed packages\n", "|`typeof(obj)` | `type(obj)`\n", "|`obj isa SomeType`
or
`isa(obj, SomeType)` | `isinstance(obj, SomeType)`\n", "\n", "If you ever ask for help or file an issue about Julia, you should generally provide the output of `versioninfo()`.\n", "\n", "And of course, you can also learn and get help here:\n", "\n", "* Learning: https://julialang.org/learning/\n", "* Documentation: https://docs.julialang.org/\n", "* Questions & Discussions:\n", " * https://discourse.julialang.org/\n", " * http://julialang.slack.com/\n", " * https://stackoverflow.com/questions/tagged/julia" ] }, { "cell_type": "markdown", "metadata": { "id": "TB3UQ7RQZZtP" }, "source": [ "# A First Look at Julia\n", "This section will give you an idea of what Julia looks like and what some of its major qualities are: it's expressive, dynamic, flexible, and most of all, super fast." ] }, { "cell_type": "markdown", "metadata": { "id": "tBKp4lciICdi" }, "source": [ "## Estimating π\n", "Let's write our first function. It will estimate π using the equation:\n", "$π = 4 \\times \\left(1 - \\dfrac{1}{3} + \\dfrac{1}{5} - \\dfrac{1}{7} + \\dfrac{1}{9}-\\dfrac{1}{11}+\\dots\\right)$\n", "\n", "There are much better ways to estimate π, but this one is easy to implement." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "EPRfV4_XI2Rk", "outputId": "385fbf71-db01-43c1-fac1-23f83739dfbf" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "π ≈ 3.141592663589326\n", "Error is 9.999532757376528e-9\n" ] } ], "source": [ "function estimate_pi(n)\n", " s = 1.0\n", " for i in 1:n\n", " s += (isodd(i) ? -1 : 1) / (2i + 1)\n", " end\n", " 4s\n", "end\n", "\n", "p = estimate_pi(100_000_000)\n", "println(\"π ≈ $p\")\n", "println(\"Error is $(p - π)\")" ] }, { "cell_type": "markdown", "metadata": { "id": "iYJZNTLCLdU3" }, "source": [ "Compare this with the equivalent Python 3 code:" ] }, { "cell_type": "markdown", "metadata": { "id": "F98h-og4KHHn" }, "source": [ "```python\n", "# PYTHON\n", "import math\n", "\n", "def estimate_pi(n):\n", " s = 1.0\n", " for i in range(1, n + 1):\n", " s += (-1 if i % 2 else 1) / (2 * i + 1)\n", " return 4 * s\n", "\n", "p = estimate_pi(100_000_000)\n", "print(f\"π ≈ {p}\") # f-strings are available in Python 3.6+\n", "print(f\"Error is {p - math.pi}\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "zZl4GRE0LuEc" }, "source": [ "Pretty similar, right? But notice the small differences:\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`function` | `def`\n", "|`for i in X`
    `...`
`end` | `for i in X:`
    `...`\n", "|`1:n` | `range(1, n+1)`\n", "|`cond ? a : b` | `a if cond else b`\n", "|`2i + 1` | `2 * i + 1`\n", "|`4s` | `return 4 * s`\n", "|`println(a, b)` | `print(a, b, sep=\"\")`\n", "|`print(a, b)` | `print(a, b, sep=\"\", end=\"\")`\n", "|`\"$p\"` | `f\"{p}\"`\n", "|`\"$(p - π)\"` | `f\"{p - math.pi}\"`\n", "\n", "This example shows that:\n", "* Julia can be just as concise and readable as Python.\n", "* Indentation in Julia is _not_ meaningful like it is in Python. Instead, blocks end with `end`.\n", "* Many math features are built in Julia and need no imports.\n", "* There's some mathy syntactic sugar, such as `2i` (but you can write `2 * i` if you prefer).\n", "* In Julia, the `return` keyword is optional at the end of a function. The result of the last expression is returned (`4s` in this example).\n", "* Julia loves Unicode and does not hesitate to use Unicode characters like `π`. However, there are generally plain-ASCII equivalents (e.g., `π == pi`)." ] }, { "cell_type": "markdown", "metadata": { "id": "C81648ZjKljL" }, "source": [ "## Typing Unicode Characters" ] }, { "cell_type": "markdown", "metadata": { "id": "ceBdy_lFKoFz" }, "source": [ "Typing Unicode characters is easy: for latex symbols like π, just type `\\pi`. For emojis like 😃, type `\\:smiley:`.\n", "\n", "This works in the REPL, in Jupyter, but unfortunately not in Colab (yet?). As a workaround, you can run the following code to print the character you want, then copy/paste it:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6nvl3qveKHld", "outputId": "9bb47019-ad28-4481-b885-5731a533efbf" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"π\"" ] }, "metadata": {}, "execution_count": 5 } ], "source": [ "using REPL.REPLCompletions: latex_symbols, emoji_symbols\n", "\n", "latex_symbols[\"\\\\pi\"]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "RUHi_sDKLGL5", "outputId": "517b32c8-e06a-4414-da52-e0518cfa6ea2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"😃\"" ] }, "metadata": {}, "execution_count": 6 } ], "source": [ "emoji_symbols[\"\\\\:smiley:\"]" ] }, { "cell_type": "markdown", "metadata": { "id": "9at61Y3LLJWX" }, "source": [ "In Julia, `using Foo.Bar: a, b` corresponds to running `from foo.bar import a, b` in Python.\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`using Foo` | `from foo import *; import foo`\n", "|`using Foo.Bar` | `from foo.bar import *; from foo import bar`\n", "|`using Foo.Bar: a, b` | `from foo.bar import a, b`\n", "|`using Foo: Bar` | `from foo import bar`\n", "\n", "More on this later." ] }, { "cell_type": "markdown", "metadata": { "id": "0CnQq32IaDGD" }, "source": [ "## Running Python code in Julia\n", "Julia lets you easily run Python code using the `PyCall` module. We installed it earlier, so we just need to import it:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "id": "SHCsfibyaBRk" }, "outputs": [], "source": [ "using PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "x7Qk9ht-d8Vo" }, "source": [ "Now that we have imported `PyCall`, we can use the `pyimport()` function to import a Python module directly in Julia! For example, let's check which Python version we are using:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LlB4nMZVeZmY", "outputId": "82ff60a1-c7fd-4211-932d-abc9af01cd3d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"3.12.12 (main, Oct 10 2025, 08:52:57) [GCC 11.4.0]\"" ] }, "metadata": {}, "execution_count": 8 } ], "source": [ "sys = pyimport(\"sys\")\n", "sys.version" ] }, { "cell_type": "markdown", "metadata": { "id": "YwM2lGhmjIAA" }, "source": [ "In fact, let's run the Python code we discussed earlier (this will take about 15 to 20 seconds to run, because Python is so slow...):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "id": "MrhaObEaJ_sB", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "33227e83-0615-46a2-8b80-9e8273f11889" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "π ≈ 3.141592663589326\n", "Error is 9.999532757376528e-09\n" ] } ], "source": [ "py\"\"\"\n", "import math\n", "\n", "def estimate_pi(n):\n", " s = 1.0\n", " for i in range(1, n + 1):\n", " s += (-1 if i % 2 else 1) / (2 * i + 1)\n", " return 4 * s\n", "\n", "p = estimate_pi(100_000_000)\n", "print(f\"π ≈ {p}\") # f-strings are available in Python 3.6+\n", "print(f\"Error is {p - math.pi}\")\n", "\"\"\"\n" ] }, { "cell_type": "markdown", "metadata": { "id": "PHemaGjSja7O" }, "source": [ "As you can see, running arbitrary Python code is as simple as using py-strings (`py\"...\"`). Note that py-strings are not part of the Julia language itself: they are defined by the `PyCall` module (we will see how this works later).\n", "\n", "We can look at the value of `p`:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5z_QlxhJLRJ5", "outputId": "072038db-1882-464b-b1bb-dcf6bdc25b5e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3.141592663589326" ] }, "metadata": {}, "execution_count": 10 } ], "source": [ "py\"p\"" ] }, { "cell_type": "markdown", "metadata": { "id": "jBx7UcHtkMa-" }, "source": [ "Let's compare this to the value we calculated above using Julia:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sKNlJjjSkIo0", "outputId": "6d0cd7b0-b627-458b-8b1b-eb5d71a1f618" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "0.0" ] }, "metadata": {}, "execution_count": 11 } ], "source": [ "py\"p\" - p" ] }, { "cell_type": "markdown", "metadata": { "id": "AOzbgPMMksUb" }, "source": [ "Perfect, they are exactly equal!\n", "\n", "As you can see, it's very easy to mix Julia and Python code. So if there's a module you really love in Python, you can keep using it as long as you want! For example, let's use NumPy:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "uXSDig4XrKyS", "outputId": "1aa08e05-1a90-4a68-80a8-edc27b6c1a20" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×3 Matrix{Float64}:\n", " 0.204074 0.0920921 0.584858\n", " 0.489169 0.89381 0.676898" ] }, "metadata": {}, "execution_count": 12 } ], "source": [ "np = pyimport(\"numpy\")\n", "a = np.random.rand(2, 3)" ] }, { "cell_type": "markdown", "metadata": { "id": "zPMwgIaeyRBh" }, "source": [ "Notice that `PyCall` automatically converts some Python types to Julia types, including NumPy arrays. That's really quite convenient! Note that Julia supports multi-dimensional arrays (analog to NumPy arrays) out of the box. `Array{Float64, 2}` means that it's a 2-dimensional array of 64-bit floats.\n", "\n", "`PyCall` also converts Julia arrays to NumPy arrays when needed:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "aWAC4wxqrxCb", "outputId": "2bfb9e4a-791e-4f5a-aeef-08cace64bd2e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×3 Matrix{Float64}:\n", " 1.22639 1.09647 1.79474\n", " 1.63096 2.44442 1.96776" ] }, "metadata": {}, "execution_count": 13 } ], "source": [ "exp_a = np.exp(a)" ] }, { "cell_type": "markdown", "metadata": { "id": "gy8gZ8pp3Z0n" }, "source": [ "If you want to use some Julia variable in a py-string, for example `exp_a`, you can do so by writing `$exp_a` like this:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "dxQ25HpJ3oPA", "outputId": "b25bea60-eaf3-4bab-bad6-a5eed61b4e06" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×3 Matrix{Float64}:\n", " 0.204074 0.0920921 0.584858\n", " 0.489169 0.89381 0.676898" ] }, "metadata": {}, "execution_count": 14 } ], "source": [ "py\"\"\"\n", "import numpy as np\n", "\n", "result = np.log($exp_a)\n", "\"\"\"\n", "\n", "py\"result\"" ] }, { "cell_type": "markdown", "metadata": { "id": "jtxk8kebxFdM" }, "source": [ "If you want to keep using Matplotlib, it's best to use the `PyPlot` module (which we installed earlier), rather than trying to use `pyimport(\"matplotlib\")`, as `PyPlot` provides a more straightforward interface with Julia, and it plays nicely with Jupyter and Colab:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 452 }, "id": "gyHkEL7ExPg2", "outputId": "981b2a02-ecd5-4365-ae99-95c28108bc1c" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "Figure(PyObject
)" ], "image/png": "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" }, "metadata": {} } ], "source": [ "using PyPlot\n", "\n", "x = range(-5π, 5π, length=100)\n", "plt.plot(x, sin.(x) ./ x) # we'll discuss this syntax in the next section\n", "plt.title(\"sin(x) / x\")\n", "plt.grid(\"True\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "id": "b42BIiqN6JM_" }, "source": [ "That said, Julia has its own plotting libraries, such as the `Plots` library, which you may want to check out." ] }, { "cell_type": "markdown", "metadata": { "id": "FGStdgNK9IG1" }, "source": [ "As you can see, Julia's `range()` function acts much like NumPy's `linspace()` function, when you use the `length` argument. However, it acts like Python's `range()` function when you use the `step` argument instead (except the upper bound is inclusive). Julia's `range()` function returns an object which behaves just like an array, except it doesn't actually use any RAM for its elements, it just stores the range parameters. If you want to collect all of the elements into an array, use the `collect()` function (similar to Python's `list()` function):" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "h728H-f9-rC_", "outputId": "e5b21c7f-e9d1-40ad-c8bb-a0fff46a0513" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[10, 30, 50, 70]\n", "[10, 30, 50, 70]\n", "[10.0, 27.5, 45.0, 62.5, 80.0]\n", "[10.0, 27.5, 45.0, 62.5, 80.0]\n" ] } ], "source": [ "println(collect(range(10, 80, step=20)))\n", "println(collect(10:20:80)) # 10:20:80 is equivalent to the previous range\n", "println(collect(range(10, 80, length=5))) # similar to NumPy's linspace()\n", "step = (80-10)/(5-1) # 17.5\n", "println(collect(10:step:80)) # equivalent to the previous range" ] }, { "cell_type": "markdown", "metadata": { "id": "gAU4t9zl72PW" }, "source": [ "The equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "print(list(range(10, 80+1, 20)))\n", "# there's no short-hand for range() in Python\n", "print(np.linspace(10, 80, 5))\n", "step = (80-10)/(5-1) # 17.5\n", "print([i*step + 10 for i in range(5)])\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "ucA1zMkw-n_U" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`np = pyimport(\"numpy\")` | `import numpy as np`\n", "|`using PyPlot` | `from pylab import *`\n", "|`1:10` | `range(1, 11)`\n", "|`1:2:10`
or
`range(1, 10, step=2)` | `range(1, 11, 2)`\n", "|`1.2:0.5:10.3`
or
`range(1.2, 10.3, step=0.5)` | `np.arange(1.2, 10.3, 0.5)`\n", "|`range(1, 10, length=3)` | `np.linspace(1, 10, 3)`\n", "|`collect(1:5)`
or
`[i for i in 1:5]` | `list(range(1, 6))`
or
`[i for i in range(1, 6)]`\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "1iiE76xA8pwr" }, "source": [ "## Loop Fusion\n", "Did you notice that we wrote `sin.(x) ./ x` (not `sin(x) / x`)? This is equivalent to `[sin(i) / i for i in x]`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "id": "kjXg63Hmd2ux" }, "outputs": [], "source": [ "a = sin.(x) ./ x\n", "b = [sin(i) / i for i in x]\n", "@assert a == b" ] }, { "cell_type": "markdown", "metadata": { "id": "wX26wG02d1KI" }, "source": [ " This is not just syntactic sugar: it's actually a very powerful Julia feature. Indeed, notice that the array only gets traversed once. Even if we chained more than two dotted operations, the array would still only get traversed once. This is called _loop fusion_.\n", "\n", "In contrast, when using NumPy arrays, `sin(x) / x` first computes a temporary array containing `sin(x)` and then it computes the final array. Two loops and two arrays instead of one. NumPy is implemented in C, and has been heavily optimized, but if you chain many operations, it still ends up being slower and using more RAM than Julia.\n", "\n", "However, all the extra dots can sometimes make the code a bit harder to read. To avoid that, you can write `@.` before an expression: every operation will be \"dotted\" automatically, like this:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "id": "raOw65bd-Qeg" }, "outputs": [], "source": [ "a = @. sin(x) / x\n", "b = sin.(x) ./ x\n", "@assert a == b" ] }, { "cell_type": "markdown", "metadata": { "id": "6ys5cFYaogb2" }, "source": [ "**Note**: Julia's `@assert` statement starts with an `@` sign, just like `@.`, which means that they are macros. In Julia, macros are very powerful metaprogramming tools: a macro is evaluated at parse time, and it can inspect the expression that follows it and then transform it, or even replace it. In practice, you will often _use_ macros, but you will rarely _define_ your own. I'll come back to macros later." ] }, { "cell_type": "markdown", "metadata": { "id": "MQ1U3aAwk5iD" }, "source": [ "## Julia is fast!\n", "Let's compare the Julia and Python implementations of the `estimate_pi()` function:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pXQbFKYgk9gG", "outputId": "08bfcdac-a80a-4d07-fd29-91ee516028fa" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 0.126690 seconds\n" ] } ], "source": [ "@time estimate_pi(100_000_000);" ] }, { "cell_type": "markdown", "metadata": { "id": "2B7Vu5lepqOW" }, "source": [ "To get a more precise benchmark, it's preferable to use the `BenchmarkTools` module. Just like Python's `timeit` module, it provides tools to benchmark code by running it multiple times. This provides a better estimate of how long each call takes:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xK4b8X_cppNQ", "outputId": "d589d9cb-ab90-4972-eeac-6086e9c44911" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "BenchmarkTools.Trial: 40 samples with 1 evaluation per sample.\n", " Range \u001b[90m(\u001b[39m\u001b[36m\u001b[1mmin\u001b[22m\u001b[39m … \u001b[35mmax\u001b[39m\u001b[90m): \u001b[39m\u001b[36m\u001b[1m126.203 ms\u001b[22m\u001b[39m … \u001b[35m128.595 ms\u001b[39m \u001b[90m┊\u001b[39m GC \u001b[90m(\u001b[39mmin … max\u001b[90m): \u001b[39m0.00% … 0.00%\n", " Time \u001b[90m(\u001b[39m\u001b[34m\u001b[1mmedian\u001b[22m\u001b[39m\u001b[90m): \u001b[39m\u001b[34m\u001b[1m126.876 ms \u001b[22m\u001b[39m\u001b[90m┊\u001b[39m GC \u001b[90m(\u001b[39mmedian\u001b[90m): \u001b[39m0.00%\n", " Time \u001b[90m(\u001b[39m\u001b[32m\u001b[1mmean\u001b[22m\u001b[39m ± \u001b[32mσ\u001b[39m\u001b[90m): \u001b[39m\u001b[32m\u001b[1m127.012 ms\u001b[22m\u001b[39m ± \u001b[32m637.152 μs\u001b[39m \u001b[90m┊\u001b[39m GC \u001b[90m(\u001b[39mmean ± σ\u001b[90m): \u001b[39m0.00% ± 0.00%\n", "\n", " \u001b[39m \u001b[39m \u001b[39m▃\u001b[39m \u001b[39m \u001b[39m \u001b[39m▃\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m█\u001b[39m▃\u001b[39m█\u001b[39m \u001b[39m \u001b[39m \u001b[34m█\u001b[39m\u001b[39m \u001b[39m \u001b[39m \u001b[32m \u001b[39m\u001b[39m \u001b[39m \u001b[39m▃\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m▃\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \n", " \u001b[39m▇\u001b[39m▁\u001b[39m█\u001b[39m▇\u001b[39m▇\u001b[39m▇\u001b[39m█\u001b[39m▇\u001b[39m▁\u001b[39m▇\u001b[39m▁\u001b[39m█\u001b[39m█\u001b[39m█\u001b[39m▁\u001b[39m▇\u001b[39m▇\u001b[34m█\u001b[39m\u001b[39m▇\u001b[39m▇\u001b[39m▇\u001b[32m▁\u001b[39m\u001b[39m▇\u001b[39m▇\u001b[39m█\u001b[39m▁\u001b[39m▇\u001b[39m▇\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m█\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▇\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▇\u001b[39m▇\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▁\u001b[39m▇\u001b[39m▇\u001b[39m▇\u001b[39m \u001b[39m▁\n", " 126 ms\u001b[90m Histogram: frequency by time\u001b[39m 129 ms \u001b[0m\u001b[1m<\u001b[22m\n", "\n", " Memory estimate\u001b[90m: \u001b[39m\u001b[33m0 bytes\u001b[39m, allocs estimate\u001b[90m: \u001b[39m\u001b[33m0\u001b[39m." ] }, "metadata": {}, "execution_count": 20 } ], "source": [ "using BenchmarkTools\n", "\n", "@benchmark estimate_pi(100_000_000)" ] }, { "cell_type": "markdown", "metadata": { "id": "xHsab0EkqObG" }, "source": [ "If this output is too verbose for you, simply use `@btime` instead:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FqbViO67qN6E", "outputId": "7b84de85-b402-46b9-b9a8-fe5a8220708b" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 126.264 ms (0 allocations: 0 bytes)\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "3.141592663589326" ] }, "metadata": {}, "execution_count": 21 } ], "source": [ "@btime estimate_pi(100_000_000)" ] }, { "cell_type": "markdown", "metadata": { "id": "JOEUCAn6qghC" }, "source": [ "Now let's time the Python version. Since the call is so slow, we just run it once (it will take about 15 seconds):" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6dk8QgRL0mzd", "outputId": "d7758bd4-63cb-44b1-a41c-504055efa99f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "21.100875977999976" ] }, "metadata": {}, "execution_count": 22 } ], "source": [ "py\"\"\"\n", "from timeit import timeit\n", "\n", "duration = timeit(\"estimate_pi(100_000_000)\", number=1, globals=globals())\n", "\"\"\"\n", "\n", "py\"duration\"" ] }, { "cell_type": "markdown", "metadata": { "id": "Jqtx4_erlxky" }, "source": [ "It looks like Julia is over 150 times faster than Python in this case! To be fair, `PyCall` does add some overhead, but even if you run this code in a separate Python shell, you will see that Julia crushes (pure) Python when it comes to speed." ] }, { "cell_type": "markdown", "metadata": { "id": "hiVDodlPmzoP" }, "source": [ "So why is Julia so much faster than Python? Well, **Julia compiles the code on the fly as it runs it**." ] }, { "cell_type": "markdown", "metadata": { "id": "w-HSoqJ8ts7P" }, "source": [ "Okay, let's summarize what we learned so far: Julia is a dynamic language that looks and feels a lot like Python, you can even execute Python code super easily, and pure Julia code runs much faster than pure Python code, because it is compiled on the fly. I hope this convinces you to read on!\n", "\n", "Next, let's continue to see how Python's main constructs can be implemented in Julia." ] }, { "cell_type": "markdown", "metadata": { "id": "n1exnPNvtsvh" }, "source": [ "# Numbers" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "NKscy9dmtsk8", "outputId": "fabf7853-0f6f-403d-db96-d9c94996df73" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "9//8" ] }, "metadata": {}, "execution_count": 23 } ], "source": [ "i = 42 # 64-bit integer\n", "f = 3.14 # 64-bit float\n", "c = 3.4 + 4.5im # 128-bit complex number\n", "\n", "bi = BigInt(2)^1000 # arbitrarily long integer\n", "bf = BigFloat(1) / 7 # arbitrary precision\n", "\n", "r = 15//6 * 9//20 # rational number" ] }, { "cell_type": "markdown", "metadata": { "id": "PzLbmPApyPLc" }, "source": [ "And the equivalent Python code:\n", "\n", "```python\n", "# PYTHON\n", "i = 42\n", "f = 3.14\n", "c = 3.4 + 4.5j\n", "\n", "bi = 2**1000 # integers are seemlessly promoted to long integers\n", "from decimal import Decimal\n", "bf = Decimal(1) / 7\n", "\n", "from fractions import Fraction\n", "r = Fraction(15, 6) * Fraction(9, 20)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "obkfXw_LAZ2B" }, "source": [ "Dividing integers gives floats, like in Python:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MQ_YTxNgAiLA", "outputId": "f66c93b7-cd7e-439f-b58a-ec82364b29b1" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2.5" ] }, "metadata": {}, "execution_count": 24 } ], "source": [ "5 / 2" ] }, { "cell_type": "markdown", "metadata": { "id": "lqAUqZqNAkhb" }, "source": [ "For integer division, use `÷` or `div()`:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xPkuXvQHArxo", "outputId": "080186f0-35b0-4acf-86e5-50e0cb5c5a1f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 25 } ], "source": [ "5 ÷ 2" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Bg26ib-cAtoF", "outputId": "eb01182f-a6d8-487a-812d-2b78fba6877c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 26 } ], "source": [ "div(5, 2)" ] }, { "cell_type": "markdown", "metadata": { "id": "GwsmLJ4kAvLQ" }, "source": [ "The `%` operator is the remainder, not the modulo like in Python. These differ only for negative numbers:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BqYAfPWBA5UZ", "outputId": "0f689d39-7ae0-4a7f-9ea3-a171c8ffdcda" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "7" ] }, "metadata": {}, "execution_count": 27 } ], "source": [ "57 % 10" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "i_0nKGW0A8TZ", "outputId": "2d869984-6426-430d-9e3c-5ed6204e5a13" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "-7" ] }, "metadata": {}, "execution_count": 28 } ], "source": [ "(-57) % 10" ] }, { "cell_type": "markdown", "metadata": { "id": "5euGtSBl95e_" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`3.4 + 4.5im` | `3.4 + 4.5j`\n", "|`BigInt(2)^1000` | `2**1000`\n", "|`BigFloat(3.14)` | `from decimal import Decimal`
`Decimal(3.14)`\n", "|`9//8` | `from fractions import Fraction`
`Fraction(9, 8)`\n", "|`5/2 == 2.5` | `5/2 == 2.5`\n", "|`5÷2 == 2`
or
`div(5, 2) == 2` | `5//2 == 2`\n", "|`57%10 == 7` | `57%10 == 7`\n", "|`(-57)%10 == -7` | `(-57)%10 == 3`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "koGxWy1D1nd2" }, "source": [ "# Strings\n", "Julia strings use double quotes `\"` or triple quotes `\"\"\"`, but not single quotes `'`:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cAJ5W9PstsbQ", "outputId": "9d756e26-9a76-4862-854a-33cc1527f903" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ångström\n" ] } ], "source": [ "s = \"ångström\" # Julia strings are UTF-8 encoded by default\n", "println(s)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "_7bPa-W7c3dZ", "outputId": "e16b6205-7b03-466a-9de5-f51ada78e131" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Julia strings\n", " can span\n", " several lines\n", "\n", "\n", " and they support the \"usual\" escapes like\n", " A, 家, and 😊!\n" ] } ], "source": [ "s = \"Julia strings\n", " can span\n", " several lines\\n\\n\n", " and they support the \\\"usual\\\" escapes like\n", " \\x41, \\u5bb6, and \\U0001f60a!\"\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "Sr6f6JBzfsc_" }, "source": [ "Use `repeat()` instead of `*` to repeat a string, and use `*` instead of `+` for concatenation:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "R52WXKlMc5h9", "outputId": "3c82d098-e3e4-47cf-fd4b-14dfa7b4a022" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "tick, tick, tick, tick, tick, tick, tick, tick, tick, tick, BOOM!\n" ] } ], "source": [ "s = repeat(\"tick, \", 10) * \"BOOM!\"\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "1QvDoWolgC1v" }, "source": [ "The equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "s = \"tick, \" * 10 + \"BOOM!\"\n", "print(s)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "UULsUFcTgPXN" }, "source": [ "Use `join(a, s)` instead of `s.join(a)`:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pMZNYWTxd3N2", "outputId": "61ecebe7-5574-4d0c-94a3-3eb1528286ba" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1, 2, 3, 4\n" ] } ], "source": [ "s = join([i for i in 1:4], \", \")\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "ejbvMUySgaTn" }, "source": [ "You can also specify a string for the last join:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wRx3SN5fdWFf", "outputId": "c511cd1f-537a-4eaa-bf88-04ad883475c4" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"1, 2, 3 and 4\"" ] }, "metadata": {}, "execution_count": 33 } ], "source": [ "s = join([i for i in 1:4], \", \", \" and \")" ] }, { "cell_type": "markdown", "metadata": { "id": "GJAbtqLg6T5s" }, "source": [ "`split()` works as you might expect:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Kqkgq7IA6FWl", "outputId": "120ce09b-785b-4271-cf4b-fa2f05dc296e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{SubString{String}}:\n", " \"one\"\n", " \"three\"\n", " \"four\"" ] }, "metadata": {}, "execution_count": 34 } ], "source": [ "split(\" one three four \")" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3cy_kYpZ6A7U", "outputId": "c8c4924c-0bd0-40f1-b82a-575dfe70a2a7" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{SubString{String}}:\n", " \"one\"\n", " \"\"\n", " \"three\"\n", " \"four!\"" ] }, "metadata": {}, "execution_count": 35 } ], "source": [ "split(\"one,,three,four!\", \",\")" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gdI7lnQ6n7RD", "outputId": "d95c2ffd-2683-4a82-9672-9e2f3a5c453e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 36 } ], "source": [ "occursin(\"sip\", \"Mississippi\")" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gnLjg6UpvKrL", "outputId": "37a6ff00-2172-4d16-b89f-9cb65c36b86a" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"I like tea\"" ] }, "metadata": {}, "execution_count": 37 } ], "source": [ "replace(\"I like coffee\", \"coffee\" => \"tea\")" ] }, { "cell_type": "markdown", "metadata": { "id": "_dMkcaL4gm_n" }, "source": [ "Triple quotes work a bit like in Python, but they also remove indentation and ignore the first line feed:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7Qc3Nn81ed3j", "outputId": "e958377d-c7e8-4358-896c-dc63cbfab4a4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\n", "1. the first line feed is ignored if it immediately follows \"\"\"\n", "2. triple quotes let you use \"quotes\" easily\n", "3. indentation is ignored\n", " - up to left-most character\n", " - ignoring the first line (the one with \"\"\")\n", "4. the final line feed it n̲o̲t̲ ignored\n", "\n", "\n" ] } ], "source": [ "s = \"\"\"\n", " 1. the first line feed is ignored if it immediately follows \\\"\"\"\n", " 2. triple quotes let you use \"quotes\" easily\n", " 3. indentation is ignored\n", " - up to left-most character\n", " - ignoring the first line (the one with \\\"\"\")\n", " 4. the final line feed it n̲o̲t̲ ignored\n", " \"\"\"\n", "println(\"\")\n", "println(s)\n", "println(\"\")" ] }, { "cell_type": "markdown", "metadata": { "id": "T0cx6rWpjIzc" }, "source": [ "## String Interpolation\n", "String interpolation uses `$variable` and `$(expression)`:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-Nl2AlRvg2c_", "outputId": "1faac71e-e77f-4804-9922-8408a80c6ff5" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1 + 2 + 3 = 6 = 6\n" ] } ], "source": [ "total = 1 + 2 + 3\n", "s = \"1 + 2 + 3 = $total = $(1 + 2 + 3)\"\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "NToitF2YjRP1" }, "source": [ "This means you must escape the `$` sign:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "AUeXBJIdjUmB", "outputId": "189e3324-bccb-40a2-b0ac-cd75643fbd14" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "The car costs $10,000\n" ] } ], "source": [ "s = \"The car costs \\$10,000\"\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "fk8BuKKtjhmo" }, "source": [ "## Raw Strings\n", "Raw strings use `raw\"...\"` instead of `r\"...\"`:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KQ9U5Ivai86F", "outputId": "dd9897ad-d0be-44bd-8811-3919dff3acd7" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "In a raw string, you only need to escape quotes \", but not\n", " $ or \\. There is one exception, however: the backslash \\\n", " must be escaped if it's just before quotes like \\\".\n" ] } ], "source": [ "s = raw\"In a raw string, you only need to escape quotes \\\", but not\n", " $ or \\. There is one exception, however: the backslash \\\n", " must be escaped if it's just before quotes like \\\\\\\".\"\n", "println(s)" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "0cqdV9lJkclw", "outputId": "8636ea74-ed7e-4543-ae40-95756673b2bd" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Triple quoted raw strings are possible too: $, \\, \\t, \"\n", " - They handle indentation and the first line feed like regular\n", " triple quoted strings.\n", " - You only need to escape triple quotes like \"\"\", and the\n", " backslash before quotes like \\\".\n", "\n" ] } ], "source": [ "s = raw\"\"\"\n", " Triple quoted raw strings are possible too: $, \\, \\t, \"\n", " - They handle indentation and the first line feed like regular\n", " triple quoted strings.\n", " - You only need to escape triple quotes like \\\"\"\", and the\n", " backslash before quotes like \\\\\".\n", " \"\"\"\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "U5eOtwDalgw-" }, "source": [ "## Characters\n", "Single quotes are used for individual Unicode characters:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PnNfVRZx2CsU", "outputId": "381b2bc9-12ec-4a79-d76c-a11f1e0ceaa5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'å': Unicode U+00E5 (category Ll: Letter, lowercase)" ] }, "metadata": {}, "execution_count": 43 } ], "source": [ "a = 'å' # Unicode code point (single quotes)" ] }, { "cell_type": "markdown", "metadata": { "id": "WA5RXfz4mBcE" }, "source": [ "To be more precise:\n", "* A Julia \"character\" represents a single Unicode code point (sometimes called a Unicode scalar).\n", "* Multiple code points may be required to produce a single _grapheme_, i.e., something that readers would recognize as a single character. Such a sequence of code points is called a \"Grapheme cluster\".\n", "\n", "For example, the character `é` can be represented either using the single code point `\\u00E9`, or the grapheme cluster `e` + `\\u0301`:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "liN9OQVClokc", "outputId": "3a32f8bf-f86b-4207-8128-26c4aa212744" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "café has 4 code points\n" ] } ], "source": [ "s = \"café\"\n", "println(s, \" has \", length(s), \" code points\")" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "CTtCiqN1oCJa", "outputId": "cd29c091-5964-4efd-9944-c94ec07331c8" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "café has 5 code points\n" ] } ], "source": [ "s = \"cafe\\u0301\"\n", "println(s, \" has \", length(s), \" code points\")" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 106 }, "id": "MIBI7ULN2CYI", "outputId": "3d902bd6-3ea9-425c-ce78-82fd47ca5545" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)" ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)" ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)" ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)" ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "'́': Unicode U+0301 (category Mn: Mark, nonspacing)" ] }, "metadata": {} } ], "source": [ "for c in \"cafe\\u0301\"\n", " display(c)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Oz-6luAbobDe" }, "source": [ "Julia represents any individual character like `'é'` using 32-bits (4 bytes):" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Iu1otE7omZIy", "outputId": "95c30719-6e72-40ff-f6ca-a41a085b6b2e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4" ] }, "metadata": {}, "execution_count": 47 } ], "source": [ "sizeof('é')" ] }, { "cell_type": "markdown", "metadata": { "id": "81XkzaaNmW_4" }, "source": [ "But strings are represented using the UTF-8 encoding. In this encoding, code points 0 to 127 are represented using one byte, but any code point above 127 is represented using 2 to 6 bytes:" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ZgLS0PdImraY", "outputId": "5329fc38-5367-4025-fa81-89fbc91fb53c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1" ] }, "metadata": {}, "execution_count": 48 } ], "source": [ "sizeof(\"a\")" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PSOu8j--mmzO", "outputId": "3f7a7f74-a919-455a-af59-0cc6de1c88c3" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 49 } ], "source": [ "sizeof(\"é\")" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5UVyrhoKmw1j", "outputId": "0a26e941-394a-4d09-e381-8bc7e11c70c7" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3" ] }, "metadata": {}, "execution_count": 50 } ], "source": [ "sizeof(\"家\")" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "0HUpcANGnJYA", "outputId": "06765ffc-b011-4552-915c-f07e8acc794e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "14" ] }, "metadata": {}, "execution_count": 51 } ], "source": [ "sizeof(\"🏳️‍🌈\") # this is a grapheme with 4 code points of 4 + 3 + 3 + 4 bytes" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "EfBbY0vHpNkG", "outputId": "8204aedb-9e00-47aa-c77a-e10cb06f6ad9" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 4\n", " 3\n", " 3\n", " 4" ] }, "metadata": {}, "execution_count": 52 } ], "source": [ "[sizeof(string(c)) for c in \"🏳️‍🌈\"]" ] }, { "cell_type": "markdown", "metadata": { "id": "71mBRSnKo_gU" }, "source": [ "You can iterate through graphemes instead of code points:" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hvHwnu4dnYNx", "outputId": "a3c766e8-d12c-4b77-dcb8-10a9c3ab567d" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "é\n", "🏳️‍🌈\n" ] } ], "source": [ "using Unicode\n", "\n", "for g in graphemes(\"e\\u0301🏳️‍🌈\")\n", " println(g)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "D52gwsw7mkls" }, "source": [ "## String Indexing\n", "Characters in a string are indexed based on the position of their starting byte in the UTF-8 representation. For example, the character `ê` in the string `\"être\"` is located at index 1, but the character `'t'` is located at index 3, since the UTF-8 encoding of `ê` is 2 bytes long:" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wF-sqnBhobOr", "outputId": "996ea873-bef2-4bef-bf08-298d474f19d3" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ê\n", "t\n", "r\n", "e\n" ] } ], "source": [ "s = \"être\"\n", "println(s[1])\n", "println(s[3])\n", "println(s[4])\n", "println(s[5])" ] }, { "cell_type": "markdown", "metadata": { "id": "PTwNWMfywghC" }, "source": [ "If you try to get the character at index 2, you get an exception:\n", "\n" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "nVTYypUmobae", "outputId": "b24a10c9-5310-4ba2-e86e-2375ef4fc677" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "StringIndexError(\"être\", 2)" ] }, "metadata": {}, "execution_count": 55 } ], "source": [ "try\n", " s[2]\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "h27VGo9oB2HD" }, "source": [ "By the way, notice the exception-handling syntax (we'll discuss exceptions later):\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`try`
    `...`
`catch ex`
    `...`
`end`|`try:`
    `...`
`except Exception as ex:`
    `...`\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "v519pXuAxhnO" }, "source": [ "You can get a substring easily, using valid character indices:" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fSYrcLRPxdBv", "outputId": "c81fa62f-47ed-42f1-860c-1a9ab4fe9e61" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"êt\"" ] }, "metadata": {}, "execution_count": 56 } ], "source": [ "s[1:3]" ] }, { "cell_type": "markdown", "metadata": { "id": "1aSg-yZExJaj" }, "source": [ "You can iterate through a string, and it will return all the code points:" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "nWzSSfi3obnG", "outputId": "64587110-f9c9-4aab-ccc7-648125d6ad1b" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ê\n", "t\n", "r\n", "e\n" ] } ], "source": [ "for c in s\n", " println(c)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "P4y_oW-tx1FM" }, "source": [ "Or you can iterate through the valid character indices:" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Stdn7xZRx5HP", "outputId": "46d4d93d-7119-41bd-bf29-058607a8aec9" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1: ê\n", "3: t\n", "4: r\n", "5: e\n" ] } ], "source": [ "for i in eachindex(s)\n", " println(i, \": \", s[i])\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "6vAcDvvgyI2O" }, "source": [ "Benefits of representing strings as UTF-8:\n", "* All Unicode characters are supported.\n", "* UTF-8 is fairly compact (at least for Latin scripts).\n", "* It plays nicely with C libraries which expect ASCII characters only, since ASCII characters correspond to the Unicode code points 0 to 127, which UTF-8 encodes exactly like ASCII.\n", "\n", "Drawbacks:\n", "* UTF-8 uses a variable number of bytes per character, which makes indexing harder.\n", " * However, If the language tried to hide this by making `s[5]` search for the 5th character from the start of the string, then code like `for i in 1:length(s); s[i]; end` would be unexpectedly inefficient, since at each iteration there would be a search from the beginning of the string, leading to O(_n_2) performance instead of O(_n_)." ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "JhOHqjktdrBV", "outputId": "8a71c95d-f47c-4f6c-a767-2d4e3c158aac" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3" ] }, "metadata": {}, "execution_count": 59 } ], "source": [ "findfirst(isequal('t'), \"être\")" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PUyEI4dHm-nl", "outputId": "276dc1a0-631a-4dcf-cc6e-6be497a0b309" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "10" ] }, "metadata": {}, "execution_count": 60 } ], "source": [ "findlast(isequal('p'), \"Mississippi\")" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VA-KDam0nT9z", "outputId": "cc4dd790-3aea-4165-ae57-b9c2f60fc599" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 61 } ], "source": [ "findnext(isequal('i'), \"Mississippi\", 2)" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "V-18_V29ny--", "outputId": "f353a36f-5a53-4f0c-e7bf-268204864316" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "5" ] }, "metadata": {}, "execution_count": 62 } ], "source": [ "findnext(isequal('i'), \"Mississippi\", 2 + 1)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "rF-0AVyCnoRa", "outputId": "a6f731fe-c41a-4951-f68e-c1f9d6fe5a75" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 63 } ], "source": [ "findprev(isequal('i'), \"Mississippi\", 5 - 1)" ] }, { "cell_type": "markdown", "metadata": { "id": "29n4QUfloQXQ" }, "source": [ "Other useful string functions: `ncodeunits(str)`, `codeunit(str, i)`, `thisind(str, i)`, `nextind(str, i, n=1)`, `prevind(str, i, n=1)`." ] }, { "cell_type": "markdown", "metadata": { "id": "v93Xg1E5omZB" }, "source": [ "## Regular Expressions\n", "To create a regular expression in Julia, use the `r\"...\"` syntax:" ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LDgR-AwboyDw", "outputId": "a7671c28-6076-4771-ffc4-6b11929a85df" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "r\"c[ao]ff?(?:é|ee)\"" ] }, "metadata": {}, "execution_count": 64 } ], "source": [ "regex = r\"c[ao]ff?(?:é|ee)\"" ] }, { "cell_type": "markdown", "metadata": { "id": "bKT4fMKMiKWo" }, "source": [ "The expression `r\"...\"` is equivalent to `Regex(\"...\")` except the former is evaluated at parse time, while the latter is evaluated at runtime, so unless you need to construct a Regex dynamically, you should prefer `r\"...\"`." ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "222akOU9ox3m", "outputId": "a202e2ca-88a9-45c1-f077-8b8c06c32585" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 65 } ], "source": [ "occursin(regex, \"A bit more coffee?\")" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "QCXRSdFuoxqd", "outputId": "1c29313f-a427-4eb1-bf43-70039ee12733" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"coffee\"" ] }, "metadata": {}, "execution_count": 66 } ], "source": [ "m = match(regex, \"A bit more coffee?\")\n", "m.match" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "lWk0C1iwtegu", "outputId": "b1327551-b64c-4f42-c440-dbd0806854ec" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "12" ] }, "metadata": {}, "execution_count": 67 } ], "source": [ "m.offset" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Uh1uc34etiN_", "outputId": "291cd3bf-41c4-459e-ff15-574f5d4d5c0e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "I suggest coffee instead\n" ] } ], "source": [ "m = match(regex, \"A bit more tea?\")\n", "isnothing(m) && println(\"I suggest coffee instead\")" ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3iOYSQMy0yB3", "outputId": "b00ea3b9-2738-4aa1-e406-edcbc3ba829f" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "code: \"f(1) \"\n", "comment: \" nice comment\"\n" ] } ], "source": [ "regex = r\"(.*)#(.+)\"\n", "line = \"f(1) # nice comment\"\n", "m = match(regex, line)\n", "code, comment = m.captures\n", "println(\"code: \", repr(code))\n", "println(\"comment: \", repr(comment))" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xeq9CVzoueaf", "outputId": "34f56585-9170-45ec-f711-cca97dd225e5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\" nice comment\"" ] }, "metadata": {}, "execution_count": 70 } ], "source": [ "m[2]" ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iwUDm5uqtc73", "outputId": "b32cdc31-a50e-4629-a169-7385640befac" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2-element Vector{Int64}:\n", " 1\n", " 7" ] }, "metadata": {}, "execution_count": 71 } ], "source": [ "m.offsets" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VnefZenKuMVM", "outputId": "81a80fd5-11e1-485a-ff62-cd41800e4ee5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\" nice comment\"" ] }, "metadata": {}, "execution_count": 72 } ], "source": [ "m = match(r\"(?.+)#(?.+)\", line)\n", "m[:comment]" ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "HI8hBnXZwLIO", "outputId": "1649c50b-c62a-4b1f-990b-0470e9fb50ae" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"Want a little bread?\"" ] }, "metadata": {}, "execution_count": 73 } ], "source": [ "replace(\"Want more bread?\", r\"(?more|some)\" => s\"a little\")" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7V9msvsWuzSM", "outputId": "c5989f21-3067-4787-e2c0-14b0bac7b057" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"Want more and more bread?\"" ] }, "metadata": {}, "execution_count": 74 } ], "source": [ "replace(\"Want more bread?\", r\"(?more|less)\" => s\"\\g and \\g\")" ] }, { "cell_type": "markdown", "metadata": { "id": "56GuNuTqaKaX" }, "source": [ "# Control Flow" ] }, { "cell_type": "markdown", "metadata": { "id": "AhlyD6rgHM-A" }, "source": [ "## `if` statement\n", "\n", "Julia's `if` statement works just like in Python, with a few differences:\n", "\n", "* Julia uses `elseif` instead of Python's `elif`.\n", "* Julia's logic operators are just like in C-like languages: `&&` means `and`, `||` means `or`, `!` means `not`, and so on." ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "R4CT1_AYLUqo", "outputId": "983efbd8-ca50-4129-e43e-c131550104ec" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "One\n" ] } ], "source": [ "a = 1\n", "if a == 1\n", " println(\"One\")\n", "elseif a == 2\n", " println(\"Two\")\n", "else\n", " println(\"Other\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "xjjegYr9Koo0" }, "source": [ "Julia also has `⊻` for exclusive or (you can type `\\xor` to get the ⊻ character):" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "id": "ac0uFmmXLk3n" }, "outputs": [], "source": [ "@assert false ⊻ false == false\n", "@assert false ⊻ true == true\n", "@assert true ⊻ false == true\n", "@assert true ⊻ true == false" ] }, { "cell_type": "markdown", "metadata": { "id": "6k6sjnOWLuFM" }, "source": [ "Oh, and notice that `true` and `false` are all lowercase, unlike Python's `True` and `False`." ] }, { "cell_type": "markdown", "metadata": { "id": "57j5Uo_jKq98" }, "source": [ "Since `&&` is lazy (like `and` in Python), `cond && f()` is a common shorthand for `if cond; f(); end`. Think of it as \"_cond then f()_\":" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "TjRz2LF2MC17", "outputId": "e6d520a0-150d-4fb8-a872-37e2a566cfa0" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Two\n" ] } ], "source": [ "a = 2\n", "a == 1 && println(\"One\")\n", "a == 2 && println(\"Two\")" ] }, { "cell_type": "markdown", "metadata": { "id": "PoOc99vdMB3r" }, "source": [ "Similarly, `cond || f()` is a common shorthand for `if !cond; f(); end`. Think of it as \"_cond else f()_\":" ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "I0vTrqXFMQAp", "outputId": "e1523e22-bd94-4418-c8f7-d8a923e408e5" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Not two\n" ] } ], "source": [ "a = 1\n", "a == 1 || println(\"Not one\")\n", "a == 2 || println(\"Not two\")" ] }, { "cell_type": "markdown", "metadata": { "id": "KDHXuzB2tprt" }, "source": [ "All expressions return a value in Julia, including `if` statements. For example:" ] }, { "cell_type": "code", "execution_count": 79, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XdbyrVzWtyYS", "outputId": "f75a8890-53ce-4330-f955-b66f0e5d5747" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"one\"" ] }, "metadata": {}, "execution_count": 79 } ], "source": [ "a = 1\n", "result = if a == 1\n", " \"one\"\n", " else\n", " \"two\"\n", " end\n", "result" ] }, { "cell_type": "markdown", "metadata": { "id": "ouJGQWRluC8r" }, "source": [ "When an expression cannot return anything, it returns `nothing`:" ] }, { "cell_type": "code", "execution_count": 80, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FXxf9pNouMUZ", "outputId": "1f39bea9-610a-47e5-fab6-cd09a1e8f907" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 80 } ], "source": [ "a = 1\n", "result = if a == 2\n", " \"two\"\n", " end\n", "\n", "isnothing(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "82e9jcH4ukdN" }, "source": [ "`nothing` is the single instance of the type `Nothing`:" ] }, { "cell_type": "code", "execution_count": 81, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IU57ogdWuhCi", "outputId": "cc390058-5c30-419d-b583-88953a098fe3" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Nothing" ] }, "metadata": {}, "execution_count": 81 } ], "source": [ "typeof(nothing)" ] }, { "cell_type": "markdown", "metadata": { "id": "m8ghzU9PMifO" }, "source": [ "## `for` loops\n", "You can use `for` loops just like in Python, as we saw earlier. However, it's also possible to create nested loops on a single line:" ] }, { "cell_type": "code", "execution_count": 82, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LZ-3HDmZM45a", "outputId": "0c0b9c59-2557-4ea5-e43b-e2aba8ead9f8" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "(1, 1, 1)\n", "(1, 1, 2)\n", "(1, 2, 1)\n", "(1, 2, 2)\n", "(1, 3, 1)\n", "(1, 3, 2)\n", "(2, 1, 1)\n", "(2, 1, 2)\n", "(2, 2, 1)\n", "(2, 2, 2)\n", "(2, 3, 1)\n", "(2, 3, 2)\n" ] } ], "source": [ "for a in 1:2, b in 1:3, c in 1:2\n", " println((a, b, c))\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "uQRBca7fObQq" }, "source": [ "The corresponding Python code would look like this:\n", "\n", "```python\n", "# PYTHON\n", "from itertools import product\n", "\n", "for a, b, c in product(range(1, 3), range(1, 4), range(1, 3)):\n", " print((a, b, c))\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "JAeag3dfNRB3" }, "source": [ "The `continue` and `break` keywords work just like in Python. Note that in single-line nested loops, `break` will exit all loops, not just the inner loop:" ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MbKmgN-jN2ZQ", "outputId": "efe3c997-488e-461b-b272-452d6f95bad6" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "(1, 1, 1)\n", "(1, 1, 2)\n", "(1, 2, 1)\n", "(1, 2, 2)\n", "(1, 3, 1)\n", "(1, 3, 2)\n", "(2, 1, 1)\n" ] } ], "source": [ "for a in 1:2, b in 1:3, c in 1:2\n", " println((a, b, c))\n", " (a, b, c) == (2, 1, 1) && break\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Lf7F9I-6Ue46" }, "source": [ "Julia does not support the equivalent of Python's `for`/`else` construct. You need to write something like this:" ] }, { "cell_type": "code", "execution_count": 84, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XltdIGVOUoVS", "outputId": "42327d40-3b05-494f-d16f-053dd1d8ae00" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Looking at Joe\n", "Looking at Jane\n", "Looking at Wally\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 84 } ], "source": [ "found = false\n", "for person in [\"Joe\", \"Jane\", \"Wally\", \"Jack\", \"Julia\"] # try removing \"Wally\"\n", " println(\"Looking at $person\")\n", " person == \"Wally\" && (found = true; break)\n", "end\n", "found || println(\"I did not find Wally.\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ySqtkxpxWH57" }, "source": [ "The equivalent Python code looks like this:\n", "\n", "```python\n", "# PYTHON\n", "for person in [\"Joe\", \"Jane\", \"Wally\", \"Jack\", \"Julia\"]: # try removing \"Wally\"\n", " print(f\"Looking at {person}\")\n", " if person == \"Wally\":\n", " break\n", "else:\n", " print(\"I did not find Wally.\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "gz4fZGxOHLmI" }, "source": [ "\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`if cond1`
    `...`
`elseif cond2`
    `...`
`else`
    `...`
`end` |`if cond1:`
    `...`
`elif cond2:`
    `...`
`else:`
    `...`\n", "|`&&` | `and`\n", "|`\\|\\|` | `or`\n", "|`!` | `not`\n", "|`⊻` (type `\\xor`) | `^`\n", "|`true` | `True`\n", "|`false` | `False`\n", "|`cond && f()` | `if cond: f()`\n", "|`cond \\|\\| f()` | `if not cond: f()`\n", "|`for i in 1:5 ... end` | `for i in range(1, 6): ...`\n", "|`for i in 1:5, j in 1:6 ... end` | `from itertools import product`
`for i, j in product(range(1, 6), range(1, 7)):`
    `...`\n", "|`while cond ... end` | `while cond: ...`\n", "|`continue` | `continue`\n", "|`break` | `break`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "KZnBWeHcvcDE" }, "source": [ "Now lets looks at data structures, starting with tuples." ] }, { "cell_type": "markdown", "metadata": { "id": "ERjZUJYlnMsq" }, "source": [ "# Tuples" ] }, { "cell_type": "markdown", "metadata": { "id": "LnIg-YKumD8N" }, "source": [ " Julia has tuples, very much like Python. They can contain anything:" ] }, { "cell_type": "code", "execution_count": 85, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "SwtMszm6nFmD", "outputId": "467677ea-c8af-4b1b-e9e1-8fd1bfea2227" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(1, \"Two\", 3, 4, 5)" ] }, "metadata": {}, "execution_count": 85 } ], "source": [ "t = (1, \"Two\", 3, 4, 5)" ] }, { "cell_type": "markdown", "metadata": { "id": "YFGgt6ISLBQN" }, "source": [ "Let's look at one element:" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "muUQX9TUnfhh", "outputId": "e24642c3-beed-48ea-eee9-add25639d890" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1" ] }, "metadata": {}, "execution_count": 86 } ], "source": [ "t[1]" ] }, { "cell_type": "markdown", "metadata": { "id": "kxt7Dyujpcto" }, "source": [ "Hey! Did you see that? **Julia is 1-indexed**, like Matlab and other math-oriented programming languages, not 0-indexed like Python and most programming languages. I found it easy to get used to, and in fact I quite like it, but your mileage may vary.\n", "\n", "Moreover, the indexing bounds are inclusive. In Python, to get the 1st and 2nd elements of a list or tuple, you would write `t[0:2]` (or just `t[:2]`), while in Julia you write `t[1:2]`.\n" ] }, { "cell_type": "code", "execution_count": 87, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PSSUDRRtp77N", "outputId": "d55b3636-8e87-4c43-b24f-e1a259bc98e7" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(1, \"Two\")" ] }, "metadata": {}, "execution_count": 87 } ], "source": [ "t[1:2]" ] }, { "cell_type": "markdown", "metadata": { "id": "gUK1aCRutlul" }, "source": [ "Note that `end` represents the index of the last element in the tuple. So you must write `t[end]` instead of `t[-1]`. Similarly, you must write `t[end - 1]`, not `t[-2]`, and so on." ] }, { "cell_type": "code", "execution_count": 88, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7YpghPgLqZmy", "outputId": "7cc4cb0d-d8ef-4c76-8f3d-d2e389ef62da" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "5" ] }, "metadata": {}, "execution_count": 88 } ], "source": [ "t[end]" ] }, { "cell_type": "code", "execution_count": 89, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3rzqwS7etsyx", "outputId": "0d4e5abb-9da6-42b6-c3d5-3a2fe371558c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(4, 5)" ] }, "metadata": {}, "execution_count": 89 } ], "source": [ "t[end - 1:end]" ] }, { "cell_type": "markdown", "metadata": { "id": "d950dLwB3SeA" }, "source": [ "Like in Python, tuples are immutable:" ] }, { "cell_type": "code", "execution_count": 90, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "0sLz7J2KmKMi", "outputId": "35d1173a-d049-45e2-e6a7-c46ac61b29bc" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(setindex!, ((1, \"Two\", 3, 4, 5), 2, 2), 0x0000000000006870)" ] }, "metadata": {}, "execution_count": 90 } ], "source": [ "try\n", " t[2] = 2\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "ia-7dPaYN6o3" }, "source": [ "The syntax for empty and 1-element tuples is the same as in Python:" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2iz34eBoODk6", "outputId": "5c11cb1e-70ef-4eca-e1c1-6a7ef8f6e94e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(42,)" ] }, "metadata": {}, "execution_count": 91 } ], "source": [ "empty_tuple = ()\n", "one_element_tuple = (42,)" ] }, { "cell_type": "markdown", "metadata": { "id": "4vgV76ftmMno" }, "source": [ "You can unpack a tuple, just like in Python (it's called \"destructuring\" in Julia):" ] }, { "cell_type": "code", "execution_count": 92, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "1-RPxSCfn5Zr", "outputId": "3195b1a0-8394-4ea5-f2cc-cde848cd7c07" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=1, b=Two, c=3, d=4, e=5\n" ] } ], "source": [ "a, b, c, d, e = (1, \"Two\", 3, 4, 5)\n", "println(\"a=$a, b=$b, c=$c, d=$d, e=$e\")" ] }, { "cell_type": "markdown", "metadata": { "id": "I4jsaD4Fq37B" }, "source": [ "It also works with nested tuples, just like in Python:" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "s3S4SAfNq83S", "outputId": "89278c27-ea62-4c64-dd23-ca7e5168d900" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=1, b=Two, c=3, d=4, e=5\n" ] } ], "source": [ "(a, (b, c), (d, e)) = (1, (\"Two\", 3), (4, 5))\n", "println(\"a=$a, b=$b, c=$c, d=$d, e=$e\")" ] }, { "cell_type": "markdown", "metadata": { "id": "1SC6AVpIrUxa" }, "source": [ "However, consider this example:" ] }, { "cell_type": "code", "execution_count": 94, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Wz5cl0_6rj0H", "outputId": "ada24ba8-e4e1-4401-9a77-7607461cf7a4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=1, b=Two, c=3\n" ] } ], "source": [ "a, b, c = (1, \"Two\", 3, 4, 5)\n", "println(\"a=$a, b=$b, c=$c\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ZdZg-xMDrnvK" }, "source": [ "In Python, this would cause a `ValueError: too many values to unpack`. In Julia, the extra values in the tuple are just ignored.\n", "\n", "If you want to capture the extra values in the variable `c`, you need to do so explicitly:" ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zC8DWzkSsF1i", "outputId": "2d28f2ec-facc-4642-bd66-445e1c0aae76" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=1, b=Two, c=(3, 4, 5)\n" ] } ], "source": [ "t = (1, \"Two\", 3, 4, 5)\n", "a, b = t[1:2]\n", "c = t[3:end]\n", "println(\"a=$a, b=$b, c=$c\")" ] }, { "cell_type": "markdown", "metadata": { "id": "rOKhLbritHAW" }, "source": [ "Or more concisely:" ] }, { "cell_type": "code", "execution_count": 96, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "8Pvh4LCytCiq", "outputId": "0f8d30a7-8484-4326-b584-edeb25ba7040" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=1, b=Two, c=(3, 4, 5)\n" ] } ], "source": [ "(a, b), c = t[1:2], t[3:end]\n", "println(\"a=$a, b=$b, c=$c\")" ] }, { "cell_type": "markdown", "metadata": { "id": "FbibRKc9sWI_" }, "source": [ "The corresponding Python code is:\n", "\n", "```python\n", "# PYTHON\n", "t = (1, \"Two\", 3, 4, 5)\n", "a, b, *c = t\n", "print(f\"a={a}, b={b}, c={c}\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "niHEbUJk5YFn" }, "source": [ "## Named Tuples" ] }, { "cell_type": "markdown", "metadata": { "id": "p3yVvsXA52ct" }, "source": [ "Julia supports named tuples:" ] }, { "cell_type": "code", "execution_count": 97, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2FJnd4K35aOZ", "outputId": "b9c4f1f9-7479-4f7e-df30-1f6b1f868e53" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(name = \"Julia\", category = \"Language\", stars = 5)" ] }, "metadata": {}, "execution_count": 97 } ], "source": [ "nt = (name=\"Julia\", category=\"Language\", stars=5)" ] }, { "cell_type": "code", "execution_count": 98, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xydPCMQZ5sPY", "outputId": "c5f38187-e131-4c71-c27a-91bb9c3d96ea" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"Julia\"" ] }, "metadata": {}, "execution_count": 98 } ], "source": [ "nt.name" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9MeN88AU5w4t", "outputId": "a9a8e2e9-3585-4db3-cf4b-d313c6808e89" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "@NamedTuple{name::String, category::String, stars::Int64}\n", " name: String \"Julia\"\n", " category: String \"Language\"\n", " stars: Int64 5\n" ] } ], "source": [ "dump(nt)" ] }, { "cell_type": "markdown", "metadata": { "id": "yXYEkJ0G57f2" }, "source": [ "The corresponding Python code is:\n", "\n", "```python\n", "# PYTHON\n", "from collections import namedtuple\n", "\n", "Rating = namedtuple(\"Rating\", [\"name\", \"category\", \"stars\"])\n", "nt = Rating(name=\"Julia\", category=\"Language\", stars=5)\n", "print(nt.name) # prints: Julia\n", "print(nt) # prints: Rating(name='Julia', category='Language', stars=5)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "MJFUxd06YdqY" }, "source": [ "# Structs\n", "Julia supports structs, which hold multiple named fields, a bit like named tuples:" ] }, { "cell_type": "code", "execution_count": 100, "metadata": { "id": "WhCs3HtJZK95" }, "outputs": [], "source": [ "struct Person\n", " name\n", " age\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "G5oQzSt9Zx3L" }, "source": [ "Structs have a default constructor, which expects all the field values, in order:" ] }, { "cell_type": "code", "execution_count": 101, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "aXISSlv5Z8l6", "outputId": "11a1ffbd-bda4-40c8-b604-052b68b890f2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Person(\"Mary\", 30)" ] }, "metadata": {}, "execution_count": 101 } ], "source": [ "p = Person(\"Mary\", 30)" ] }, { "cell_type": "code", "execution_count": 102, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "26v5EQ8YZ-JQ", "outputId": "2d9b1eec-9c27-4131-fdca-92ac60febada" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "30" ] }, "metadata": {}, "execution_count": 102 } ], "source": [ "p.age" ] }, { "cell_type": "markdown", "metadata": { "id": "Tq30l3f1dHcn" }, "source": [ "You can create other constructors by creating functions with the same name as the struct:" ] }, { "cell_type": "code", "execution_count": 103, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oS_GHuHpdPQx", "outputId": "9476844e-bc24-4d7d-c85c-2338244fb8a0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Person(\"no name\", -1)" ] }, "metadata": {}, "execution_count": 103 } ], "source": [ "function Person(name)\n", " Person(name, -1)\n", "end\n", "\n", "function Person()\n", " Person(\"no name\")\n", "end\n", "\n", "p = Person()" ] }, { "cell_type": "markdown", "metadata": { "id": "MhHkPNXzeMwh" }, "source": [ "This creates two constructors: the second calls the first, which calls the default constructor. Notice that you can create multiple functions with the same name but different arguments. We will discuss this later." ] }, { "cell_type": "markdown", "metadata": { "id": "C_qV3JIyeial" }, "source": [ "These two constructors are called \"outer constructors\", since they are defined outside of the definition of the struct. You can also define \"inner constructors\":" ] }, { "cell_type": "code", "execution_count": 104, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XuGp8ZJJe13y", "outputId": "69581ad5-f232-4224-cd14-0f402830ae78" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Person2(\"no name\", -1)" ] }, "metadata": {}, "execution_count": 104 } ], "source": [ "struct Person2\n", " name\n", " age\n", " function Person2(name)\n", " new(name, -1)\n", " end\n", "end\n", "\n", "function Person2()\n", " Person2(\"no name\")\n", "end\n", "\n", "p = Person2()" ] }, { "cell_type": "markdown", "metadata": { "id": "m5qB9QHf1MMC" }, "source": [ "This time, the outer constructor calls the inner constructor, which calls the `new()` function. This `new()` function only works in inner constructors, and of course it creates an instance of the struct.\n", "\n", "When you define inner constructors, they replace the default constructor:" ] }, { "cell_type": "code", "execution_count": 105, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "_xLhbC9q2vC3", "outputId": "b80cd94e-49b8-4ff5-b7a2-28c38e452286" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(Person2, (\"Bob\", 40), 0x0000000000006875)" ] }, "metadata": {}, "execution_count": 105 } ], "source": [ "try\n", " Person2(\"Bob\", 40)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "6eSrsaNi_mQ3" }, "source": [ "Structs usually have very few inner constructors (often just one), which do the heavy duty work, and the checks. Then they may have multiple outer constructors which are mostly there for convenience." ] }, { "cell_type": "markdown", "metadata": { "id": "HLJWvFSfaAHi" }, "source": [ "By default, structs are immutable:" ] }, { "cell_type": "code", "execution_count": 106, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "n9hhxkdvaYOY", "outputId": "ce700033-4f04-40cb-f6e1-7ce545d44de0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "ErrorException(\"setfield!: immutable struct of type Person2 cannot be changed\")" ] }, "metadata": {}, "execution_count": 106 } ], "source": [ "try\n", " p.name = \"Someone\"\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "oqY3EFMKa1Px" }, "source": [ "However, it is possible to define a mutable struct:" ] }, { "cell_type": "code", "execution_count": 107, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "S5cNqzAma5H_", "outputId": "f9330bb1-ff82-4984-9533-7173f41dba32" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Person3(\"Lucy\", 80)" ] }, "metadata": {}, "execution_count": 107 } ], "source": [ "mutable struct Person3\n", " name\n", " age\n", "end\n", "\n", "p = Person3(\"Lucy\", 79)\n", "p.age += 1\n", "p" ] }, { "cell_type": "markdown", "metadata": { "id": "XcMyQq4r7eam" }, "source": [ "Structs look a lot like Python classes, with instance variables and constructors, but where are the methods? We will discuss this later, in the \"Methods\" section." ] }, { "cell_type": "markdown", "metadata": { "id": "sXEjDf7jBjJi" }, "source": [ "# Arrays\n", "\n", "Let's create a small array:" ] }, { "cell_type": "code", "execution_count": 108, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UFhriSyixCMX", "outputId": "b0f94d3a-1323-4ba5-fa04-8061dd97dced" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 108 } ], "source": [ "a = [1, 4, 9, 16]" ] }, { "cell_type": "markdown", "metadata": { "id": "owVeZ9nLR2on" }, "source": [ "Indexing and assignments work as you would expect:" ] }, { "cell_type": "code", "execution_count": 109, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yM4mNBfKR81J", "outputId": "59a14970-63a1-40b0-ae3d-9750b24dda14" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 10\n", " 20\n", " 30\n", " 16" ] }, "metadata": {}, "execution_count": 109 } ], "source": [ "a[1] = 10\n", "a[2:3] = [20, 30]\n", "a" ] }, { "cell_type": "markdown", "metadata": { "id": "bv_tPMOhxHtK" }, "source": [ "## Element Type\n", "Since we used only integers when creating the array, Julia inferred that the array is only meant to hold integers (NumPy arrays behave the same way). Let's try adding a string:" ] }, { "cell_type": "code", "execution_count": 110, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XBiagA9b_YPR", "outputId": "5f015246-92b4-43ea-9f2c-eba65165ef99" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(convert, (Int64, \"Three\"), 0x0000000000006876)" ] }, "metadata": {}, "execution_count": 110 } ], "source": [ "try\n", " a[3] = \"Three\"\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "T8EJ7y5HAgzz" }, "source": [ "Nope! We get a `MethodError` exception, telling us that Julia could not convert the string `\"Three\"` to a 64-bit integer (we will discuss exceptions later). If we want an array that can hold any type, like Python's lists can, we must prefix the array with `Any`, which is Julia's root type (like `object` in Python):" ] }, { "cell_type": "code", "execution_count": 111, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VcsscYgVBX6Z", "outputId": "b0e93ecf-8757-45b9-e6e0-d2e7636a7081" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Any}:\n", " 1\n", " 4\n", " \"Three\"\n", " 16" ] }, "metadata": {}, "execution_count": 111 } ], "source": [ "a = Any[1, 4, 9, 16]\n", "a[3] = \"Three\"\n", "a" ] }, { "cell_type": "markdown", "metadata": { "id": "yekHWpLFPWUO" }, "source": [ "Prefixing with `Float64`, or `String` or any other type works as well:" ] }, { "cell_type": "code", "execution_count": 112, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hR9x5VEmPfgO", "outputId": "aec74e37-b8f7-4055-86f0-826e98c72518" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Float64}:\n", " 1.0\n", " 4.0\n", " 9.0\n", " 16.0" ] }, "metadata": {}, "execution_count": 112 } ], "source": [ "Float64[1, 4, 9, 16]" ] }, { "cell_type": "markdown", "metadata": { "id": "8-2ahduHoiRh" }, "source": [ "An empty array is automatically an `Any` array:" ] }, { "cell_type": "code", "execution_count": 113, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eDMPVf5Oolnk", "outputId": "4308af4f-0cac-43b1-e7b2-a1fe2aa3226f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Any[]" ] }, "metadata": {}, "execution_count": 113 } ], "source": [ "a = []" ] }, { "cell_type": "markdown", "metadata": { "id": "zY4m8VX2PmXq" }, "source": [ "You can use the `eltype()` function to get an array's element type (the equivalent of NumPy arrays' `dtype`):" ] }, { "cell_type": "code", "execution_count": 114, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zZN9rtpEPvGV", "outputId": "19e3bafc-2d4e-4f3f-93db-d3bf0eda48d8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Int64" ] }, "metadata": {}, "execution_count": 114 } ], "source": [ "eltype([1, 4, 9, 16])" ] }, { "cell_type": "markdown", "metadata": { "id": "RXavtfEvyx-I" }, "source": [ "If you create an array containing objects of different types, Julia will do its best to use a type that can hold all the values as precisely as possible. For example, a mix of integers and floats results in a float array:" ] }, { "cell_type": "code", "execution_count": 115, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "jovRNC9tzaWj", "outputId": "94f4f869-423f-4dbd-db7d-251589bf59b6" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Float64}:\n", " 1.0\n", " 2.0\n", " 3.0\n", " 4.0" ] }, "metadata": {}, "execution_count": 115 } ], "source": [ "[1, 2, 3.0, 4.0]" ] }, { "cell_type": "markdown", "metadata": { "id": "Z8vqDB0U0A4p" }, "source": [ "This is similar to NumPy's behavior:\n", "\n", "```python\n", "# PYTHON\n", "np.array([1, 2, 3.0, 4.0]) # => array([1., 2., 3., 4.])\n", "```\n", "\n", "A mix of unrelated types results in an `Any` array:" ] }, { "cell_type": "code", "execution_count": 116, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pgT5mGcV0Rrc", "outputId": "49d913bc-9d01-46a7-e43c-424c72ed2d97" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Any}:\n", " 1\n", " 2\n", " \"Three\"\n", " 4" ] }, "metadata": {}, "execution_count": 116 } ], "source": [ "[1, 2, \"Three\", 4]" ] }, { "cell_type": "markdown", "metadata": { "id": "qqUknXIGB2r7" }, "source": [ "If you want to live in a world without type constraints, you can prefix all you arrays with `Any`, and you will feel like you're coding in Python. But I don't recommend it: the compiler can perform a bunch of optimizations when it knows exactly the type and size of the data the program will handle, so it will run much faster. So when you create an empty array but you know the type of the values it will contain, you might as well prefix it with that type (you don't have to, but it will speed up your program)." ] }, { "cell_type": "markdown", "metadata": { "id": "3oo7WzpiQB_h" }, "source": [ "## Push and Pop\n", "To append elements to an array, use the `push!()` function. By convention, functions whose name ends with a bang `!` may modify their arguments:" ] }, { "cell_type": "code", "execution_count": 117, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "m2vQfFEVCxII", "outputId": "53d7ddcd-b0f8-41e9-a056-2611407d9aa6" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 117 } ], "source": [ "a = [1]\n", "push!(a, 4)\n", "push!(a, 9, 16)" ] }, { "cell_type": "markdown", "metadata": { "id": "2U8zIXG-cUge" }, "source": [ "This is similar to the following Python code:\n", "\n", "```python\n", "# PYTHON\n", "a = [1]\n", "a.append(4)\n", "a.extend([9, 16]) # or simply a += [9, 16]\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "CCWoFNINU15g" }, "source": [ "And `pop!()` works like in Python:" ] }, { "cell_type": "code", "execution_count": 118, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ie0iAIHtSXIW", "outputId": "198f39f3-f339-4ad1-96e0-716098db4a18" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "16" ] }, "metadata": {}, "execution_count": 118 } ], "source": [ "pop!(a)" ] }, { "cell_type": "markdown", "metadata": { "id": "0W67Y7JhVA3_" }, "source": [ "Equivalent to:\n", "\n", "```python\n", "# PYTHON\n", "a.pop()\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "mY0L1GG_Upxz" }, "source": [ "There are many more functions you can call on an array. We will see later how to find them." ] }, { "cell_type": "markdown", "metadata": { "id": "4ZScNi5Rv8cM" }, "source": [ "## Multidimensional Arrays\n", "Importantly, Julia arrays can be multidimensional, just like NumPy arrays:" ] }, { "cell_type": "code", "execution_count": 119, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hxfUbTc3wBob", "outputId": "5aef8986-9f54-47e7-da39-022c3617505f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×4 Matrix{Int64}:\n", " 1 2 3 4\n", " 5 6 7 8\n", " 9 10 11 12" ] }, "metadata": {}, "execution_count": 119 } ], "source": [ "M = [1 2 3 4\n", " 5 6 7 8\n", " 9 10 11 12]" ] }, { "cell_type": "markdown", "metadata": { "id": "sFlaq_4kwbde" }, "source": [ "Another syntax for this is:" ] }, { "cell_type": "code", "execution_count": 120, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wetBEg8HwhWJ", "outputId": "fd772cea-cbce-4fc5-db93-025b6b86b98f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×4 Matrix{Int64}:\n", " 1 2 3 4\n", " 5 6 7 8\n", " 9 10 11 12" ] }, "metadata": {}, "execution_count": 120 } ], "source": [ "M = [1 2 3 4; 5 6 7 8; 9 10 11 12]" ] }, { "cell_type": "markdown", "metadata": { "id": "atfjWzUmwIgw" }, "source": [ "You can index them much like NumPy arrays:" ] }, { "cell_type": "code", "execution_count": 121, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GhtdjnYQwLka", "outputId": "987a1124-e024-4b63-db7b-4c9c1f0b267c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×2 Matrix{Int64}:\n", " 7 8\n", " 11 12" ] }, "metadata": {}, "execution_count": 121 } ], "source": [ "M[2:3, 3:4]" ] }, { "cell_type": "markdown", "metadata": { "id": "4frazS3zXPQT" }, "source": [ "You can transpose a matrix using the \"adjoint\" operator `'`:" ] }, { "cell_type": "code", "execution_count": 122, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ydWdJ1eFXVbJ", "outputId": "0e2eaf67-212a-4438-8f08-3d64910aba5d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4×3 adjoint(::Matrix{Int64}) with eltype Int64:\n", " 1 5 9\n", " 2 6 10\n", " 3 7 11\n", " 4 8 12" ] }, "metadata": {}, "execution_count": 122 } ], "source": [ "M'" ] }, { "cell_type": "markdown", "metadata": { "id": "A032EIww_NsU" }, "source": [ "As you can see, Julia arrays are closer to NumPy arrays than to Python lists.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "NnY2DL_YCPmZ" }, "source": [ "Arrays can be concatenated vertically using the `vcat()` function:" ] }, { "cell_type": "code", "execution_count": 123, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pTazNcWZ_-8e", "outputId": "8e4f2d39-621d-4e51-a489-729e3ad47d6c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4×2 Matrix{Int64}:\n", " 1 2\n", " 3 4\n", " 5 6\n", " 7 8" ] }, "metadata": {}, "execution_count": 123 } ], "source": [ "M1 = [1 2\n", " 3 4]\n", "M2 = [5 6\n", " 7 8]\n", "vcat(M1, M2)" ] }, { "cell_type": "markdown", "metadata": { "id": "GPjPb-cCCZI5" }, "source": [ "Alternatively, you can use the `[M1; M2]` syntax:" ] }, { "cell_type": "code", "execution_count": 124, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DOnN1wVOCp9z", "outputId": "a4b34e79-faa3-4473-b3a4-9f8f591190ff" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4×2 Matrix{Int64}:\n", " 1 2\n", " 3 4\n", " 5 6\n", " 7 8" ] }, "metadata": {}, "execution_count": 124 } ], "source": [ "[M1; M2]" ] }, { "cell_type": "markdown", "metadata": { "id": "wvq9sUdOCtJR" }, "source": [ "To concatenate arrays horizontally, use `hcat()`:" ] }, { "cell_type": "code", "execution_count": 125, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Xj7s5cedCLJy", "outputId": "84637b65-0572-4aa5-e9e4-b0e47df45a84" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×4 Matrix{Int64}:\n", " 1 2 5 6\n", " 3 4 7 8" ] }, "metadata": {}, "execution_count": 125 } ], "source": [ "hcat(M1, M2)" ] }, { "cell_type": "markdown", "metadata": { "id": "ciffAqpeC2dX" }, "source": [ "Or you can use the `[M1 M2]` syntax:" ] }, { "cell_type": "code", "execution_count": 126, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XxSWzDXhC08W", "outputId": "c99ca16c-6eaf-4c9f-9051-e93455b3db66" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2×4 Matrix{Int64}:\n", " 1 2 5 6\n", " 3 4 7 8" ] }, "metadata": {}, "execution_count": 126 } ], "source": [ "[M1 M2]" ] }, { "cell_type": "markdown", "metadata": { "id": "7aZr_u3dvndO" }, "source": [ "As you can see, the semicolon `;` is used to concatenate along the 1st axis, while the space is used to concatenate along the 2nd axis." ] }, { "cell_type": "markdown", "metadata": { "id": "2clq6l0SwWnX" }, "source": [ "Since Julia 1.7, it's possible to use `;;` instead of the space to concatenate along the 2nd axis, `;;;` to concatenate along the 3rd axis, `;;;;` along the 4th axis, and so on. For example, the following code creates a 4x3x2 array in Julia 1.7+ (however, it creates a vector with numbers 1 to 24 in Julia 1.6 and earlier):" ] }, { "cell_type": "code", "execution_count": 127, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "npdZhnLyedtz", "outputId": "bae1e674-6120-422e-9afd-e98fc970ef7e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4×3×2 Array{Int64, 3}:\n", "[:, :, 1] =\n", " 1 5 9\n", " 2 6 10\n", " 3 7 11\n", " 4 8 12\n", "\n", "[:, :, 2] =\n", " 13 17 21\n", " 14 18 22\n", " 15 19 23\n", " 16 20 24" ] }, "metadata": {}, "execution_count": 127 } ], "source": [ "[1;2;3;4 ;; 5;6;7;8 ;; 9;10;11;12 ;;; 13;14;15;16 ;; 17;18;19;20 ;; 21;22;23;24]" ] }, { "cell_type": "markdown", "source": [ "Note that Julia arrays are column-major, as opposed to row-major like NumPy arrays. This means that in memory, the elements are listed by column first (as well as in the syntax in the previous cell). For example, the elements of the previous array are stored in memory in the following order: 1, 2, 3, ..., 24 (whereas NumPy would store them as 1, 13, 5, 17, 9, 21, 2, 14, 6, 18, and so on).\n", "\n", "For this reason, the fastest way to iterate over a Julia array using nested `for` loops is to use the outer `for` loop for the last dimension, and the inner `for` loop for the first dimension (the opposite is true with NumPy).\n", "\n", "For this reason, in Julia Machine Learning frameworks like Flux, the batch dimension is typically the last one, instead of the first one in Python frameworks like TensorFlow or PyTorch." ], "metadata": { "id": "GGVuPUf-mU8v" } }, { "cell_type": "markdown", "metadata": { "id": "OhQoXAiyGge9" }, "source": [ "You can combine horizontal and vertical concatenation:" ] }, { "cell_type": "code", "execution_count": 128, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ra4Xe-Cv_PKl", "outputId": "344aa338-4bf5-46b5-f92f-16dc18b1bae5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×4 Matrix{Int64}:\n", " 1 2 5 6\n", " 3 4 7 8\n", " 9 10 11 12" ] }, "metadata": {}, "execution_count": 128 } ], "source": [ "M3 = [9 10 11 12]\n", "[M1 M2; M3]" ] }, { "cell_type": "markdown", "metadata": { "id": "sQllnyjlIBfB" }, "source": [ "Equivalently, you can call the `hvcat()` function. The first argument specifies the number of arguments to concatenate in each block row:" ] }, { "cell_type": "code", "execution_count": 129, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Lv63KdpLEN4N", "outputId": "f2659123-1f4a-48a1-a579-ba996e660725" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×4 Matrix{Int64}:\n", " 1 2 5 6\n", " 3 4 7 8\n", " 9 10 11 12" ] }, "metadata": {}, "execution_count": 129 } ], "source": [ "hvcat((2, 1), M1, M2, M3)" ] }, { "cell_type": "markdown", "metadata": { "id": "e40Z6cdzWM2K" }, "source": [ "`hvcat()` is useful to create a single cell matrix:" ] }, { "cell_type": "code", "execution_count": 130, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KP-WjRlPzzSR", "outputId": "de8e708c-3407-4171-a9f2-cbc2cd24978f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1×1 Matrix{Int64}:\n", " 42" ] }, "metadata": {}, "execution_count": 130 } ], "source": [ "hvcat(1, 42)" ] }, { "cell_type": "markdown", "metadata": { "id": "4xWYHOGgzvOr" }, "source": [ " Or a column vector (i.e., an _n_×1 matrix = a matrix with a single column):" ] }, { "cell_type": "code", "execution_count": 131, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "NhVPed-TWW6b", "outputId": "311e1900-0b35-4040-e0ce-dbdae11a526d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×1 Matrix{Int64}:\n", " 10\n", " 11\n", " 12" ] }, "metadata": {}, "execution_count": 131 } ], "source": [ "hvcat((1, 1, 1), 10, 11, 12) # a column vector with values 10, 11, 12\n", "hvcat(1, 10, 11, 12) # equivalent to the previous line" ] }, { "cell_type": "markdown", "metadata": { "id": "0HmtBdKmx3Su" }, "source": [ "If you are using Julia 1.7+, you can use this syntax instead to create a single cell matrix:" ] }, { "cell_type": "code", "execution_count": 132, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7IFRhJ7yx2Wh", "outputId": "16ea9578-d69a-4c74-eb69-eb318731295d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1×1 Matrix{Int64}:\n", " 42" ] }, "metadata": {}, "execution_count": 132 } ], "source": [ "[42;;]" ] }, { "cell_type": "markdown", "metadata": { "id": "rpnZx6DwySaI" }, "source": [ "Or a column vector:" ] }, { "cell_type": "code", "execution_count": 133, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fqvKwLbByNMr", "outputId": "b8358874-85b4-4f0f-a4b4-8c559bf8985e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×1 Matrix{Int64}:\n", " 10\n", " 11\n", " 12" ] }, "metadata": {}, "execution_count": 133 } ], "source": [ "[10;11;12;;]" ] }, { "cell_type": "markdown", "metadata": { "id": "pVTtq2RyWfSD" }, "source": [ "Alternatively, you can transpose a row vector (but `hvcat()` and the Julia 1.7 syntax are a bit faster):" ] }, { "cell_type": "code", "execution_count": 134, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9d8inKAGWjTr", "outputId": "f261bd4f-9d97-4aef-b9d6-6bb0ecfc1c81" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×1 adjoint(::Matrix{Int64}) with eltype Int64:\n", " 10\n", " 11\n", " 12" ] }, "metadata": {}, "execution_count": 134 } ], "source": [ "[10 11 12]'" ] }, { "cell_type": "markdown", "metadata": { "id": "f75W5yn5OiEC" }, "source": [ "The REPL and IJulia call `display()` to print the result of the last expression in a cell (except when it is `nothing`). It is fairly verbose:" ] }, { "cell_type": "code", "execution_count": 135, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 106 }, "id": "9zTEV0mpOzm9", "outputId": "6743c3b6-3e72-4af3-8695-f753befcf1d6" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 2\n", " 3\n", " 4" ] }, "metadata": {} } ], "source": [ "display([1, 2, 3, 4])" ] }, { "cell_type": "markdown", "metadata": { "id": "gMWooHYTO7NU" }, "source": [ "The `println()` function is more concise, but be careful not to confuse vectors, column vectors and row vectors (printed with commas, semi-colons and spaces, respectively):" ] }, { "cell_type": "code", "execution_count": 136, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Xw2He_XbPQbz", "outputId": "d9ff7984-765a-402b-9a96-c8fa9a2f2f91" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Vector: [1, 2, 3, 4]\n", "Column vector: [1; 2; 3; 4;;]\n", "Row vector: [1 2 3 4]\n", "Matrix: [1 2 3; 4 5 6]\n" ] } ], "source": [ "println(\"Vector: \", [1, 2, 3, 4])\n", "println(\"Column vector: \", hvcat(1, 1, 2, 3, 4))\n", "println(\"Row vector: \", [1 2 3 4])\n", "println(\"Matrix: \", [1 2 3; 4 5 6])" ] }, { "cell_type": "markdown", "metadata": { "id": "8yjMLNP2M3Ip" }, "source": [ "Before Julia 1.7, the column vector was printed as `[1; 2; 3; 4]`, but evaluating `[1; 2; 3; 4]` would give you a regular vector. That's because `[x;y]` concatenates `x` and `y` vertically, and if `x` and `y` are scalars or vectors, you just get a regular vector. Since Julia 1.7, the column vector is printed as `[1; 2; 3; 4;;]`." ] }, { "cell_type": "markdown", "metadata": { "id": "bdnpqswqyrp8" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`a = [1, 2, 3]` | `a = [1, 2, 3]`
or
`import numpy as np`
`np.array([1, 2, 3])`\n", "|`a[1]` | `a[0]`\n", "|`a[end]` | `a[-1]`\n", "|`a[2:end-1]` | `a[1:-1]`\n", "|`push!(a, 5)` | `a.append(5)`\n", "|`pop!(a)` | `a.pop()`\n", "|`M = [1 2 3]` | `np.array([[1, 2, 3]])`\n", "|`M = [1 2 3]'` | `np.array([[1, 2, 3]]).T`\n", "|`M = hvcat(1, 1, 2, 3)`
or
`M = [1; 2; 3;;]` since Julia 1.7 | `np.array([[1], [2], [3]])`\n", "|`M = [1 2 3`
           `4 5 6]`
or
`M = [1 2 3; 4 5 6]`
or
`M = [1;4 ;; 2;5 ;; 3;6]` since Julia 1.7 | `M = np.array([[1,2,3], [4,5,6]])`\n", "|`M[1:2, 2:3]` | `M[0:2, 1:3]`\n", "|`[M1; M2]` | `np.r_[M1, M2]`\n", "|`[M1 M2]` | `np.c_[M1, M2]`\n", "|`[M1 M2; M3]` | `np.r_[np.c_[M1, M2], M3]`\n", "|`[42;;;]` since Julia 1.7 | `np.array([[[42]]])`" ] }, { "cell_type": "markdown", "metadata": { "id": "raewgAnU9YIv" }, "source": [ "## Comprehensions\n", "List comprehensions are available in Julia, just like in Python (they're usually just called \"comprehensions\" in Julia):" ] }, { "cell_type": "code", "execution_count": 137, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "H8B6Tk429aTV", "outputId": "6dd26624-4057-4db7-ea99-be55b0d37afa" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 137 } ], "source": [ "a = [x^2 for x in 1:4]" ] }, { "cell_type": "markdown", "metadata": { "id": "1W4O8W3bQsIn" }, "source": [ "You can filter elements using an `if` clause, just like in Python:" ] }, { "cell_type": "code", "execution_count": 138, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pK4bZX6LQzNF", "outputId": "dde54cb2-594d-49ff-87dc-8b23142b9d69" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{Int64}:\n", " 1\n", " 9\n", " 25" ] }, "metadata": {}, "execution_count": 138 } ], "source": [ "a = [x^2 for x in 1:5 if x ∉ (2, 4)]" ] }, { "cell_type": "markdown", "metadata": { "id": "E6UjXo-wRpdf" }, "source": [ "* `a ∉ b` is equivalent to `!(a in b)` (or `a not in b` in Python). You can type `∉` with `\\notin`\n", "* `a ∈ b` is equivalent to `a in b`. You can type it with `\\in`" ] }, { "cell_type": "markdown", "metadata": { "id": "6R3umUN4Qq1x" }, "source": [ "In Julia, comprehensions can contain nested loops, just like in Python:" ] }, { "cell_type": "code", "execution_count": 139, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "o7-yPeiSlY96", "outputId": "0c91411b-e915-4018-c23e-594ffde184a2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "6-element Vector{Tuple{Int64, Int64}}:\n", " (1, 1)\n", " (2, 1)\n", " (2, 2)\n", " (3, 1)\n", " (3, 2)\n", " (3, 3)" ] }, "metadata": {}, "execution_count": 139 } ], "source": [ "a = [(i,j) for i in 1:3 for j in 1:i]" ] }, { "cell_type": "markdown", "metadata": { "id": "tgmDLO9-ljg2" }, "source": [ "Here's the corresponding Python code:\n", "\n", "```python\n", "# PYTHON\n", "a = [(i, j) for i in range(1, 4) for j in range(1, i+1)]\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "1d06vVpLi9pS" }, "source": [ "Julia comprehensions can also create multi-dimensional arrays (note the different syntax: there is only one `for`):" ] }, { "cell_type": "code", "execution_count": 140, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gx9M3oLFjHsp", "outputId": "3f45a497-e4b0-4d8f-a995-e575e76bdaf5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3×5 Matrix{Int64}:\n", " 1 2 3 4 5\n", " 2 4 6 8 10\n", " 3 6 9 12 15" ] }, "metadata": {}, "execution_count": 140 } ], "source": [ "a = [row * col for row in 1:3, col in 1:5]" ] }, { "cell_type": "markdown", "metadata": { "id": "8_MgIEMeg69D" }, "source": [ "# Dictionaries\n", "The syntax for dictionaries is a bit different than Python:" ] }, { "cell_type": "code", "execution_count": 141, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IwBc2os7g9Yr", "outputId": "7fe46af6-e8c4-4cd4-d1ab-1158578c7653" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "arbre\n" ] } ], "source": [ "d = Dict(\"tree\"=>\"arbre\", \"love\"=>\"amour\", \"coffee\"=>\"café\")\n", "println(d[\"tree\"])" ] }, { "cell_type": "code", "execution_count": 142, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "rZoY383Qhaus", "outputId": "46e379e8-c310-4038-da2f-90ee0ee5fe00" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "pardon?\n" ] } ], "source": [ "println(get(d, \"unknown\", \"pardon?\"))" ] }, { "cell_type": "code", "execution_count": 143, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GOk28cgEjsjG", "outputId": "00406e6c-2d1d-4d38-f2bb-248d6f617177" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "KeySet for a Dict{String, String} with 3 entries. Keys:\n", " \"coffee\"\n", " \"tree\"\n", " \"love\"" ] }, "metadata": {}, "execution_count": 143 } ], "source": [ "keys(d)" ] }, { "cell_type": "code", "execution_count": 144, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xrt3-wu7j6TI", "outputId": "967aeb9e-b7d3-4d9e-b9ff-6bbe473434e5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "ValueIterator for a Dict{String, String} with 3 entries. Values:\n", " \"café\"\n", " \"arbre\"\n", " \"amour\"" ] }, "metadata": {}, "execution_count": 144 } ], "source": [ "values(d)" ] }, { "cell_type": "code", "execution_count": 145, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "J2kRT8MGj8Wo", "outputId": "f5c6a588-aacf-4b8c-874f-f95972356775" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 145 } ], "source": [ "haskey(d, \"love\")" ] }, { "cell_type": "code", "execution_count": 146, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PABOOmVSkJ6l", "outputId": "26a0822a-a1ef-4af4-9c13-2097b8242182" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 146 } ], "source": [ "\"love\" in keys(d) # this is slower than haskey()" ] }, { "cell_type": "markdown", "metadata": { "id": "xfL2ZXK6hlEt" }, "source": [ "The equivalent Python code is of course:\n", "\n", "```python\n", "d = {\"tree\": \"arbre\", \"love\": \"amour\", \"coffee\": \"café\"}\n", "d[\"tree\"]\n", "d.get(\"unknown\", \"pardon?\")\n", "d.keys()\n", "d.values()\n", "\"love\" in d\n", "\"love\" in d.keys()\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "8MI_Oti4h2sz" }, "source": [ "Dict comprehensions work as you would expect:" ] }, { "cell_type": "code", "execution_count": 147, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ck21M2RIiEWw", "outputId": "660d3999-bebf-42bf-db19-5f633138e3b9" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Dict{Int64, Int64} with 5 entries:\n", " 5 => 25\n", " 4 => 16\n", " 2 => 4\n", " 3 => 9\n", " 1 => 1" ] }, "metadata": {}, "execution_count": 147 } ], "source": [ "d = Dict(i=>i^2 for i in 1:5)" ] }, { "cell_type": "markdown", "metadata": { "id": "yeyP5pbQiK4-" }, "source": [ "Note that the items (aka \"pairs\" in Julia) are shuffled, since dictionaries are hash-based, like in Python (although Python sorts them by key for display).\n", "\n", "You can easily iterate through the dictionary's pairs like this:" ] }, { "cell_type": "code", "execution_count": 148, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GJ1PcdDHiouw", "outputId": "7279ec61-5bc1-40da-e4f9-616aa5a31830" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "5 maps to 25\n", "4 maps to 16\n", "2 maps to 4\n", "3 maps to 9\n", "1 maps to 1\n" ] } ], "source": [ "for (k, v) in d\n", " println(\"$k maps to $v\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "oSjOvDGxi-DS" }, "source": [ "The equivalent code in Python is:\n", "\n", "```python\n", "# PYTHON\n", "d = {i: i**2 for i in range(1, 6)}\n", "for k, v in d.items():\n", " print(f\"{k} maps to {v}\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "b5Ea2USET5jD" }, "source": [ "And you can merge dictionaries like this:" ] }, { "cell_type": "code", "execution_count": 149, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "dkNkluLJT7zD", "outputId": "b168c7ea-a504-47b1-9f75-09a2aa168c77" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Dict{String, String} with 4 entries:\n", " \"car\" => \"voiture\"\n", " \"coffee\" => \"café\"\n", " \"tree\" => \"arbre\"\n", " \"love\" => \"aimer\"" ] }, "metadata": {}, "execution_count": 149 } ], "source": [ "d1 = Dict(\"tree\"=>\"arbre\", \"love\"=>\"amour\", \"coffee\"=>\"café\")\n", "d2 = Dict(\"car\"=>\"voiture\", \"love\"=>\"aimer\")\n", "\n", "d = merge(d1, d2)" ] }, { "cell_type": "markdown", "metadata": { "id": "B1WRTtDXUSCt" }, "source": [ "Notice that the second dictionary has priority in case of conflict (it's `\"love\" => \"aimer\"`, not `\"love\" => \"amour\"`)." ] }, { "cell_type": "markdown", "metadata": { "id": "vpIXgc-4Umc8" }, "source": [ "In Python, this would be:\n", "\n", "```python\n", "# PYTHON\n", "d1 = {\"tree\": \"arbre\", \"love\": \"amour\", \"coffee\": \"café\"}\n", "d2 = {\"car\": \"voiture\", \"love\": \"aimer\"}\n", "d = {**d1, **d2}\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "zu39af1MVaKB" }, "source": [ "Or if you want to update the first dictionary instead of creating a new one:" ] }, { "cell_type": "code", "execution_count": 150, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5_kcU7WTVhIN", "outputId": "79dca3d2-d77b-484a-c418-9d89654f0c78" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Dict{String, String} with 4 entries:\n", " \"car\" => \"voiture\"\n", " \"coffee\" => \"café\"\n", " \"tree\" => \"arbre\"\n", " \"love\" => \"aimer\"" ] }, "metadata": {}, "execution_count": 150 } ], "source": [ "merge!(d1, d2)" ] }, { "cell_type": "markdown", "metadata": { "id": "9h_NjAmAVmBd" }, "source": [ "In Python, that's:\n", "\n", "```python\n", "# PYTHON\n", "d1.update(d2)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "6_-ibdMnjP8a" }, "source": [ "In Julia, each pair is an actual `Pair` object:" ] }, { "cell_type": "code", "execution_count": 151, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "a85m1lmJjVPP", "outputId": "562fce7a-02e3-4f71-957c-1b29d5ef1350" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Pair{String, String}\n", "tree maps to arbre\n" ] } ], "source": [ "p = \"tree\" => \"arbre\"\n", "println(typeof(p))\n", "k, v = p\n", "println(\"$k maps to $v\")" ] }, { "cell_type": "markdown", "metadata": { "id": "mOCYFiB3N5s8" }, "source": [ "Note that any object for which a `hash()` method is implemented can be used as a key in a dictionary. This includes all the basic types like integers, floats, as well as string, tuples, etc. But it also includes arrays! In Julia, you have the freedom to use arrays as keys (unlike in Python), but make sure not to mutate these arrays after insertion, or else things will break! Indeed, the pairs will be stored in memory in a location that depends on the hash of the key at insertion time, so if that key changes afterwards, you won't be able to find the pair anymore:" ] }, { "cell_type": "code", "execution_count": 152, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "0bYNOLHiOgnl", "outputId": "56f193f8-53f1-4a8c-b131-9d8dc2a183bd" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "The dictionary is: Dict([1, 2, 3] => \"My array\")\n", "Indexing works fine as long as the array is unchanged: My array\n", "This is the dictionary now: Dict([10, 2, 3] => \"My array\")\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "KeyError([10, 2, 3])" ] }, "metadata": {}, "execution_count": 152 } ], "source": [ "a = [1, 2, 3]\n", "d = Dict(a => \"My array\")\n", "println(\"The dictionary is: $d\")\n", "println(\"Indexing works fine as long as the array is unchanged: \", d[a])\n", "a[1] = 10\n", "println(\"This is the dictionary now: $d\")\n", "try\n", " println(\"Key changed, indexing is now broken: \", d[a])\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "zr26SHedQJTE" }, "source": [ "However, it's still possible to iterate through the keys, the values or the pairs:" ] }, { "cell_type": "code", "execution_count": 153, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5e6eZEq9QUt4", "outputId": "babf8d79-2bc0-47dc-9af1-5fb23b10af47" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[10, 2, 3] => \"My array\"\n" ] } ], "source": [ "for pair in d\n", " println(pair)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Ie9QcVsYWDFh" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`Dict(\"tree\"=>\"arbre\", \"love\"=>\"amour\")` | `{\"tree\": \"arbre\", \"love\": \"amour\"}`\n", "|`d[\"arbre\"]` | `d[\"arbre\"]`\n", "|`get(d, \"unknown\", \"default\")` | `d.get(\"unknown\", \"default\")`\n", "|`keys(d)` | `d.keys()`\n", "|`values(d)` | `d.values()`\n", "|`haskey(d, k)` | `k in d`\n", "|`Dict(i=>i^2 for i in 1:4)` | `{i: i**2 for i in 1:4}`\n", "|`for (k, v) in d` | `for k, v in d.items():`\n", "|`merge(d1, d2)` | `{**d1, **d2}`\n", "|`merge!(d1, d2)` | `d1.update(d2)`" ] }, { "cell_type": "markdown", "metadata": { "id": "rTWmi1P8jlDq" }, "source": [ "# Sets" ] }, { "cell_type": "markdown", "metadata": { "id": "WlTKQaxxmLde" }, "source": [ "Let's create a couple sets:" ] }, { "cell_type": "code", "execution_count": 154, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sQ_wNw0Mjmqs", "outputId": "2eba8978-ab97-43a7-b215-d2c00cb5abfc" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 5 elements:\n", " 5\n", " 7\n", " 2\n", " 11\n", " 3" ] }, "metadata": {}, "execution_count": 154 } ], "source": [ "odd = Set([1, 3, 5, 7, 9, 11])\n", "prime = Set([2, 3, 5, 7, 11])" ] }, { "cell_type": "markdown", "metadata": { "id": "w7JZBRWbsqiL" }, "source": [ "The order of sets is not guaranteed, just like in Python." ] }, { "cell_type": "markdown", "metadata": { "id": "JEQ96Ljkm_0R" }, "source": [ "Use `in` or `∈` (type `\\in`) to check whether a set contains a given value:" ] }, { "cell_type": "code", "execution_count": 155, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oRd2gpWvm9mF", "outputId": "8f635d29-188d-4fa9-f158-23746216d8a4" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 155 } ], "source": [ "5 ∈ odd" ] }, { "cell_type": "code", "execution_count": 156, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "pRQN8XNWnQgj", "outputId": "21ae3b48-9f61-4c68-cc16-a5e9ea0de98a" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 156 } ], "source": [ "5 in odd" ] }, { "cell_type": "markdown", "metadata": { "id": "WyBv6ethnRw8" }, "source": [ "Both of these expressions are equivalent to:" ] }, { "cell_type": "code", "execution_count": 157, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Twamj50pnXQE", "outputId": "f55dc5a6-44bc-41be-bea5-a01eb6d2604e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 157 } ], "source": [ "in(5, odd)" ] }, { "cell_type": "markdown", "metadata": { "id": "MHQ5qnLZmPhR" }, "source": [ "Now let's get the union of these two sets:" ] }, { "cell_type": "code", "execution_count": 158, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mfuMOXDdmGKA", "outputId": "86cd608f-e4c4-4c9c-bfb3-5b437acf323f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 7 elements:\n", " 5\n", " 7\n", " 1\n", " 11\n", " 2\n", " 9\n", " 3" ] }, "metadata": {}, "execution_count": 158 } ], "source": [ "odd ∪ prime" ] }, { "cell_type": "markdown", "metadata": { "id": "xS6GLhE5mRbD" }, "source": [ "∪ is the union symbol, not a U. To type this character, type `\\cup` (it has the shape of a cup). Alternatively, you can just use the `union()` function:" ] }, { "cell_type": "code", "execution_count": 159, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "G_yju4QvmerU", "outputId": "770d4cca-6808-4684-f739-d486694c28dc" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 7 elements:\n", " 5\n", " 7\n", " 1\n", " 11\n", " 2\n", " 9\n", " 3" ] }, "metadata": {}, "execution_count": 159 } ], "source": [ "union(odd, prime)" ] }, { "cell_type": "markdown", "metadata": { "id": "Y8l4331img0-" }, "source": [ "Now let's get the intersection using the ∩ symbol (type `\\cap`):" ] }, { "cell_type": "code", "execution_count": 160, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "NazqlxbGmkZM", "outputId": "e48cc37f-1473-408a-ba33-e65459543d91" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 4 elements:\n", " 5\n", " 7\n", " 11\n", " 3" ] }, "metadata": {}, "execution_count": 160 } ], "source": [ "odd ∩ prime" ] }, { "cell_type": "markdown", "metadata": { "id": "X9-MDgxWmzXl" }, "source": [ "Or use the `intersect()` function:" ] }, { "cell_type": "code", "execution_count": 161, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zcEEMfG4m1l9", "outputId": "3615b2ef-2195-4aa9-ef05-b2dc4663d9d8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 4 elements:\n", " 5\n", " 7\n", " 11\n", " 3" ] }, "metadata": {}, "execution_count": 161 } ], "source": [ "intersect(odd, prime)" ] }, { "cell_type": "markdown", "metadata": { "id": "QSMs88owpLb2" }, "source": [ "Next, let's get the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement) and the [symetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) between these two sets:" ] }, { "cell_type": "code", "execution_count": 162, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-Tpr5_BBpQH_", "outputId": "6649ba37-1bac-4979-dd85-e9db57285064" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 2 elements:\n", " 9\n", " 1" ] }, "metadata": {}, "execution_count": 162 } ], "source": [ "setdiff(odd, prime) # values in odd but not in prime" ] }, { "cell_type": "code", "execution_count": 163, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "of_EQE1CpvpO", "outputId": "b61850da-e0fb-43e1-c3fa-817da5b0cfe7" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 3 elements:\n", " 2\n", " 9\n", " 1" ] }, "metadata": {}, "execution_count": 163 } ], "source": [ "symdiff(odd, prime) # values that are not in the intersection" ] }, { "cell_type": "markdown", "metadata": { "id": "NH_-NbzPsSQ_" }, "source": [ "Lastly, set comprehensions work just fine:" ] }, { "cell_type": "code", "execution_count": 164, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LMIiE-0psXvs", "outputId": "3b4433f6-a18a-449a-b96e-a4f20a38ebad" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Set{Int64} with 4 elements:\n", " 4\n", " 16\n", " 9\n", " 1" ] }, "metadata": {}, "execution_count": 164 } ], "source": [ "Set([i^2 for i in 1:4])" ] }, { "cell_type": "markdown", "metadata": { "id": "DBJ4XaVmoeUY" }, "source": [ "The equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "odds = {1, 3, 5, 7, 9, 11}\n", "primes = {2, 3, 5, 7, 11}\n", "5 in primes\n", "odds | primes # union\n", "odds.union(primes)\n", "odds & primes # intersection\n", "odds.intersection(primes)\n", "odds - primes # set difference\n", "odds.difference(primes)\n", "odds ^ primes # symmetric difference\n", "odds.symmetric_difference(primes)\n", "{i**2 for i in range(1, 5)}\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "vm1zWNsJRcFV" }, "source": [ "Note that you can store any hashable object in a `Set` (i.e., any instance of a type for which the `hash()` method is implemented). This includes arrays, unlike in Python. Just like for dictionary keys, you can add arrays to sets, but make sure not to mutate them after insertion." ] }, { "cell_type": "markdown", "metadata": { "id": "nPrJ9Ih9X4ak" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`Set([1, 3, 5, 7])` | `{1, 3, 5, 7}`\n", "|`5 in odd` | `5 in odd`\n", "|`Set([i^2 for i in 1:4])` | `{i**2 for i in range(1, 5)}`\n", "|`odd ∪ primes` | `odd | primes`\n", "|`union(odd, primes)` | `odd.union(primes)`\n", "|`odd ∩ primes` | `odd & primes`\n", "|`insersect(odd, primes)` | `odd.intersection(primes)`\n", "|`setdiff(odd, primes)` | `odd - primes` or `odd.difference(primes)`\n", "|`symdiff(odd, primes)` | `odd ^ primes` or `odd.symmetric_difference(primes)`" ] }, { "cell_type": "markdown", "metadata": { "id": "AOfz9B7ZF__1" }, "source": [ "# Enums" ] }, { "cell_type": "markdown", "metadata": { "id": "RozBUdxsGgKv" }, "source": [ "To create an enum, use the `@enum` macro:" ] }, { "cell_type": "code", "execution_count": 165, "metadata": { "id": "R6HMPDyvGB_k" }, "outputs": [], "source": [ "@enum Fruit apple=1 banana=2 orange=3" ] }, { "cell_type": "markdown", "metadata": { "id": "dxFUJ9boGmkh" }, "source": [ "This creates the `Fruit` enum, with 3 possible values. It also binds the names to the values:" ] }, { "cell_type": "code", "execution_count": 166, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "czHDZ6MRGUvL", "outputId": "e9ac7de7-86d9-4a04-bfc8-387098db5976" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "banana::Fruit = 2" ] }, "metadata": {}, "execution_count": 166 } ], "source": [ "banana" ] }, { "cell_type": "markdown", "metadata": { "id": "7ufyorAsGvzv" }, "source": [ "Or you can get a `Fruit` instance using the value:" ] }, { "cell_type": "code", "execution_count": 167, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "NVgfYUBWGObS", "outputId": "97f2ccb8-afae-47ec-973d-91167415efb0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "banana::Fruit = 2" ] }, "metadata": {}, "execution_count": 167 } ], "source": [ "Fruit(2)" ] }, { "cell_type": "markdown", "metadata": { "id": "ctxEepMNG_-4" }, "source": [ "And you can get all the instances of the enum easily:" ] }, { "cell_type": "code", "execution_count": 168, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yPy9trwtGR1y", "outputId": "6ed39ba0-d992-494f-9fa5-7caf2df5b6af" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(apple, banana, orange)" ] }, "metadata": {}, "execution_count": 168 } ], "source": [ "instances(Fruit)" ] }, { "cell_type": "markdown", "metadata": { "id": "bBCsVIXEHD29" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`@enum Fruit apple=1 banana=2 orange=3` | `from enum import Enum`
`class Fruit(Enum):`
    `APPLE = 1`
    `BANANA = 2`
    `ORANGE = 3`\n", "| `Fruit(2) === banana` | `Fruit(2) is Fruit.BANANA`\n", "| `instances(Fruit)` | `dir(Fruit)`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "7OOe0m7pLj80" }, "source": [ "# Object Identity\n", "In the previous example, `Fruit(2)` and `banana` refer to the same object, not just two objects that happen to be equal. You can verify using the `===` operator, which is the equivalent of Python's `is` operator:" ] }, { "cell_type": "code", "execution_count": 169, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "G6cpTrQcGUP4", "outputId": "2055624a-da28-4d1a-9986-bfb2ab508d67" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 169 } ], "source": [ "banana === Fruit(2)" ] }, { "cell_type": "markdown", "metadata": { "id": "4QPR-8TeL5Wy" }, "source": [ "You can also check this by looking at their `objectid()`, which is the equivalent of Python's `id()` function:" ] }, { "cell_type": "code", "execution_count": 170, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "q6qYhWnyMBpf", "outputId": "f331f978-0b35-41d1-92df-266ebe7ddc41" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "0x000000000c17feaa" ] }, "metadata": {}, "execution_count": 170 } ], "source": [ "objectid(banana)" ] }, { "cell_type": "code", "execution_count": 171, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sT3hq8f_MEZN", "outputId": "40003bc7-7a06-4624-b1e5-ac53a5af6999" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "0x000000000c17feaa" ] }, "metadata": {}, "execution_count": 171 } ], "source": [ "objectid(Fruit(2))" ] }, { "cell_type": "code", "execution_count": 172, "metadata": { "id": "4ZrS4322MKfN" }, "outputs": [], "source": [ "a = [1, 2, 4]\n", "b = [1, 2, 4]\n", "@assert a == b # a and b are equal\n", "@assert a !== b # but they are not the same object" ] }, { "cell_type": "markdown", "metadata": { "id": "K3EO6cQmMW0W" }, "source": [ "|Julia|Python\n", "|-----|------\n", "|`a === b` | `a is b`\n", "|`a !== b` | `a is not b`\n", "|`objectid(obj)` | `id(obj)`" ] }, { "cell_type": "markdown", "metadata": { "id": "d_ATAuXREZx9" }, "source": [ "# Other Collections\n", "\n", "For the Julia equivalent of Python's other collections, namely `defaultdict`, `deque`, `OrderedDict`, and `Counter`, check out these libraries:\n", "\n", "* https://github.com/JuliaCollections/DataStructures.jl\n", "* https://github.com/JuliaCollections/OrderedCollections.jl\n", "* https://github.com/andyferris/Dictionaries.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "GBYBDAsF6iDC" }, "source": [ "Now let's looks at various iteration constructs." ] }, { "cell_type": "markdown", "metadata": { "id": "jApml2wDacs2" }, "source": [ "# Iteration Tools" ] }, { "cell_type": "markdown", "metadata": { "id": "AW2vVurj9E7r" }, "source": [ "## Generator Expressions\n", "\n", "Just like in Python, a generator expression resembles a list comprehension, but without the square brackets, and it returns a generator instead of a list. Here's a much shorter implementation of the `estimate_pi()` function using a generator expression:" ] }, { "cell_type": "code", "execution_count": 173, "metadata": { "id": "7pLMhQjV6z5l" }, "outputs": [], "source": [ "function estimate_pi2(n)\n", " 4 * sum((isodd(i) ? -1 : 1)/(2i+1) for i in 0:n)\n", "end\n", "\n", "@assert estimate_pi(100) == estimate_pi2(100)" ] }, { "cell_type": "markdown", "metadata": { "id": "u-WAtc-eDcp9" }, "source": [ "That's very similar to the corresponding Python code:\n", "\n", "```python\n", "# PYTHON\n", "def estimate_pi2(n):\n", " return 4 * sum((-1 if i%2==1 else 1)/(2*i+1) for i in range(n+1))\n", "\n", "assert estimate_pi(100) == estimate_pi2(100)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "OHaPPceEKO3r" }, "source": [ "## `zip`, `enumerate`, `collect`" ] }, { "cell_type": "markdown", "metadata": { "id": "GBj2mvbiI9cP" }, "source": [ "The `zip()` function works much like in Python:" ] }, { "cell_type": "code", "execution_count": 174, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-UsOasVeJCWe", "outputId": "ce79f575-6a2c-4f4e-aa4a-e07a90ca6f89" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "10: Ten\n", "11: Eleven\n", "12: Twelve\n" ] } ], "source": [ "for (i, s) in zip(10:13, [\"Ten\", \"Eleven\", \"Twelve\"])\n", " println(i, \": \", s)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "hu_eJ7okKolh" }, "source": [ "Notice that the parentheses in `for (i, s)` are required in Julia, as opposed to Python." ] }, { "cell_type": "markdown", "metadata": { "id": "3y2q4YhNJOQy" }, "source": [ "The `enumerate()` function also works like in Python, except of course it is 1-indexed:" ] }, { "cell_type": "code", "execution_count": 175, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bCTejeu0Ji_8", "outputId": "5bfab1f5-8dd1-427e-f173-8988d94e4aca" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1: One\n", "2: Two\n", "3: Three\n" ] } ], "source": [ "for (i, s) in enumerate([\"One\", \"Two\", \"Three\"])\n", " println(i, \": \", s)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "vs4_UjWnJy20" }, "source": [ "To pull the values of a generator into an array, use `collect()`:" ] }, { "cell_type": "code", "execution_count": 176, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "42T1NJKhKD1w", "outputId": "bcc6ffa3-15ea-4760-e4d1-67fe248442b9" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "5-element Vector{Int64}:\n", " 1\n", " 2\n", " 3\n", " 4\n", " 5" ] }, "metadata": {}, "execution_count": 176 } ], "source": [ "collect(1:5)" ] }, { "cell_type": "markdown", "metadata": { "id": "MQMx3bjkKFpk" }, "source": [ "A shorter syntax for that is:" ] }, { "cell_type": "code", "execution_count": 177, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2IEoo_TvKIDm", "outputId": "528c4961-ec74-469c-f260-e30d55d9491f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "5-element Vector{Int64}:\n", " 1\n", " 2\n", " 3\n", " 4\n", " 5" ] }, "metadata": {}, "execution_count": 177 } ], "source": [ "[1:5;]" ] }, { "cell_type": "markdown", "metadata": { "id": "gbub1jhlKc0V" }, "source": [ "The equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "list(range(1, 6))\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "CumclAQ4TTpq" }, "source": [ "## Generators\n", "In Python, you can easily write a generator function to create an object that will behave like an iterator. For example, let's create a generator for the Fibonacci sequence (where each number is the sum of the two previous numbers):\n", "\n", "```python\n", "def fibonacci(n):\n", " a, b = 1, 1\n", " for i in range(n):\n", " yield a\n", " a, b = b, a + b\n", "\n", "for f in fibonacci(10):\n", " print(f)\n", "```\n" ] }, { "cell_type": "markdown", "metadata": { "id": "bfjsba6gFcMS" }, "source": [ "This is also quite easy in Julia:" ] }, { "cell_type": "code", "execution_count": 178, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cQZ0cowPUC8x", "outputId": "661df601-fa66-4917-f8d9-6dcd12f2669c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n" ] } ], "source": [ "function fibonacci(n)\n", " Channel() do ch\n", " a, b = 1, 1\n", " for i in 1:n\n", " put!(ch, a)\n", " a, b = b, a + b\n", " end\n", " end\n", "end\n", "\n", "for f in fibonacci(10)\n", " println(f)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "VWkUE9FRFfQI" }, "source": [ "The `Channel` type is part of the API for tasks and coroutines. We'll discuss these later." ] }, { "cell_type": "markdown", "metadata": { "id": "lisTUktNlbQt" }, "source": [ "Now let's take a closer look at functions." ] }, { "cell_type": "markdown", "metadata": { "id": "jKpjTHMbao6r" }, "source": [ "# Functions" ] }, { "cell_type": "markdown", "metadata": { "id": "k-IIQF4vP7w9" }, "source": [ "## Arguments\n", "Julia functions supports positional arguments and default values:" ] }, { "cell_type": "code", "execution_count": 179, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BnVg7UKNQQ4M", "outputId": "4e2ebcb6-37fb-43a7-909b-04ed6051d7a0" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "x=10, y=20, width=30, height=4\n" ] } ], "source": [ "function draw_face(x, y, width=3, height=4)\n", " println(\"x=$x, y=$y, width=$width, height=$height\")\n", "end\n", "\n", "draw_face(10, 20, 30)" ] }, { "cell_type": "markdown", "metadata": { "id": "AmKSe9fYRMxb" }, "source": [ "However, unlike in Python, positional arguments must not be named when the function is called:" ] }, { "cell_type": "code", "execution_count": 180, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fB1flNxMRAo4", "outputId": "0ab2b841-09f7-4b54-d313-b0e3bbf68e76" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(Core.kwcall, ((width = 30,), Main.draw_face, 10, 20), 0x000000000000688b)" ] }, "metadata": {}, "execution_count": 180 } ], "source": [ "try\n", " draw_face(10, 20, width=30)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "yBQOd8H6RUJs" }, "source": [ "Julia also supports a variable number of arguments (called \"varargs\") using the syntax `arg...`, which is the equivalent of Python's `*arg`:" ] }, { "cell_type": "code", "execution_count": 181, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "OS1U6AxeRYyb", "outputId": "92e94fde-a2cf-497e-ee18-4b9cd81409d6" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "target_dir=/tmp, paths=(\"a.txt\", \"b.txt\")\n" ] } ], "source": [ "function copy_files(target_dir, paths...)\n", " println(\"target_dir=$target_dir, paths=$paths\")\n", "end\n", "\n", "copy_files(\"/tmp\", \"a.txt\", \"b.txt\")" ] }, { "cell_type": "markdown", "metadata": { "id": "x8lJsP6wQujj" }, "source": [ "Keyword arguments are supported, after a semicolon `;`:" ] }, { "cell_type": "code", "execution_count": 182, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "R4XDHjftQwel", "outputId": "a6a865fe-95db-4d8b-9c3f-af54f826fa70" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "paths=(\"a.txt\", \"b.txt\"), confirm=false, /tmp\n" ] } ], "source": [ "function copy_files2(paths...; confirm=false, target_dir)\n", " println(\"paths=$paths, confirm=$confirm, $target_dir\")\n", "end\n", "\n", "copy_files2(\"a.txt\", \"b.txt\"; target_dir=\"/tmp\")" ] }, { "cell_type": "markdown", "metadata": { "id": "katU7ZypHZdc" }, "source": [ "Notes:\n", "* `target_dir` has no default value, so it is a required argument.\n", "* The order of the keyword arguments does not matter." ] }, { "cell_type": "markdown", "metadata": { "id": "c24CyL7PGE35" }, "source": [ "You can have another vararg in the keyword section. It corresponds to Python's `**kwargs`:" ] }, { "cell_type": "code", "execution_count": 183, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "m-u29GPoGP8l", "outputId": "2916be01-0c50-4bc9-a7b6-0f0d6928608c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "paths=(\"a.txt\", \"b.txt\"), confirm=false, /tmp\n", "verbose=true\n" ] } ], "source": [ "function copy_files3(paths...; confirm=false, target_dir, options...)\n", " println(\"paths=$paths, confirm=$confirm, $target_dir\")\n", " verbose = options[:verbose]\n", " println(\"verbose=$verbose\")\n", "end\n", "\n", "copy_files3(\"a.txt\", \"b.txt\"; target_dir=\"/tmp\", verbose=true, timeout=60)" ] }, { "cell_type": "markdown", "metadata": { "id": "UpbuZ0RYGsC2" }, "source": [ "The `options` vararg acts like a dictionary (we will discuss dictionaries later). The keys are **symbols**, e.g., `:verbose`. Symbols are like strings, less flexible but faster. They are typically used as keys or identifiers.\n", "\n", "|Julia|Python (3.8+ if `/` is used)\n", "|-----|------\n", "| `function foo(a, b=2, c=3)`
    `...`
`end`

`foo(1, 2) # positional only` | `def foo(a, b=2, c=3, /):`
    `...`

`foo(1, 2) # pos only because of /`\n", "| `function foo(;a=1, b, c=3)`
    `...`
`end`

`foo(c=30, b=2) # keyword only` | `def foo(*, a=1, b, c=3):`
    `...`

`foo(c=30, b=2) # kw only because of *`\n", "| `function foo(a, b=2; c=3, d)`
    `...`
`end`

`foo(1; d=4) # pos only; then keyword only` | `def foo(a, b=2, /, *, c=3, d):`
    `...`

`foo(1, d=4) # pos only then kw only`\n", "| `function foo(a, b=2, c...)`
    `...`
`end`

`foo(1, 2, 3, 4) # positional only` | `def foo(a, b=2, /, *c):`
    `...`

`foo(1, 2, 3, 4) # positional only`\n", "| `function foo(a, b=1, c...; d=1, e, f...)`
    `...`
`end`

`foo(1, 2, 3, 4, e=5, x=10, y=20)`
| `def foo(a, b=1, /, *c, d=1, e, **f):`
    `...`

`foo(1, 2, 3, 4, e=5, x=10, y=20)`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "JQsdtaYXDoNm" }, "source": [ "## Concise Functions\n", "\n", "In Julia, the following definition:" ] }, { "cell_type": "code", "execution_count": 184, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PnB4hMDIkd_4", "outputId": "99acb031-78a8-42df-95e2-3b9703daabd6" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "square (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 184 } ], "source": [ "square(x) = x^2" ] }, { "cell_type": "markdown", "metadata": { "id": "iXKsRqZUkk9O" }, "source": [ "is equivalent to:" ] }, { "cell_type": "code", "execution_count": 185, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iaDlQ7Mzkong", "outputId": "668ac1fc-245b-45a6-b1d4-e6bd8eaf03ca" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "square (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 185 } ], "source": [ "function square(x)\n", " x^2\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Fjt5CFRfktfK" }, "source": [ "For example, here's a shorter way to define the `estimate_pi()` function in Julia:" ] }, { "cell_type": "code", "execution_count": 186, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "1_D5M-EFDymm", "outputId": "5f9a4c1c-4de3-4613-99c9-467ecb47bc70" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "estimate_pi3 (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 186 } ], "source": [ "estimate_pi3(n) = 4 * sum((isodd(i) ? -1 : 1)/(2i+1) for i in 0:n)" ] }, { "cell_type": "markdown", "metadata": { "id": "ZGDMqjmBls35" }, "source": [ "To define a function on one line in Python, you need to use a `lambda` (but this is generally frowned upon, since the resulting function's name is `\"\"`):\n", "\n", "```python\n", "# PYTHON\n", "square = lambda x: x**2\n", "assert square.__name__ == \"\"\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "pyLBXz8llVhu" }, "source": [ "This leads us to anonymous functions." ] }, { "cell_type": "markdown", "metadata": { "id": "UV29B5anEAFF" }, "source": [ "## Anonymous Functions\n", "You can define anonymous functions (lambdas) using the `in -> out` syntax:" ] }, { "cell_type": "code", "execution_count": 187, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VAH0e6BjEJB2", "outputId": "f48a75eb-9619-4771-f296-ad0348352ad0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 187 } ], "source": [ "map(x -> x^2, 1:4)" ] }, { "cell_type": "markdown", "metadata": { "id": "RLy4-8trawqM" }, "source": [ "Here is the equivalent Python code:\n", "\n", "```python\n", "list(map(lambda x: x**2, range(1, 5)))\n", "```\n", "\n", "Notes:\n", "* `map()` returns an array in Julia, instead of an iterator like in Python.\n", "* You could use a comprehension instead: `[x^2 for x in 1:4]`." ] }, { "cell_type": "markdown", "metadata": { "id": "7RcghJ-EEUjz" }, "source": [ "\n", "|Julia|Python\n", "|-----|------\n", "|`x -> x^2` | `lambda x: x**2`\n", "|`(x,y) -> x + y` | `lambda x,y: x + y `\n", "|`() -> println(\"yes\")` | `lambda: print(\"yes\")`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "jSYEdqYvF6XH" }, "source": [ "In Python, lambda functions must be simple expressions. They cannot contain multiple statements. In Julia, they can be as long as you want. Indeed, you can create a multi-statement block using the syntax `(stmt_1; stmt_2; ...; stmt_n)`. The return value is the output of the last statement. For example:" ] }, { "cell_type": "code", "execution_count": 188, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "SnuJMFymEbPp", "outputId": "0cabfbfc-1ddd-4813-f3cd-91eb4e6fedfd" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number 1\n", "Number 2\n", "Number 3\n", "Number 4\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 188 } ], "source": [ "map(x -> (println(\"Number $x\"); x^2), 1:4)" ] }, { "cell_type": "markdown", "metadata": { "id": "k1ztQt-VGz7p" }, "source": [ "This syntax can span multiple lines:" ] }, { "cell_type": "code", "execution_count": 189, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "H-XL-nysG67t", "outputId": "38b797ee-0ee2-48bb-efda-bea8efb9ff79" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number 1\n", "Number 2\n", "Number 3\n", "Number 4\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 189 } ], "source": [ "map(x -> (\n", " println(\"Number $x\");\n", " x^2\n", " ), 1:4)" ] }, { "cell_type": "markdown", "metadata": { "id": "YZUU9OLdHEUX" }, "source": [ "But in this case, it's probably clearer to use the `begin ... end` syntax instead:" ] }, { "cell_type": "code", "execution_count": 190, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "t3LUgpigHJ3Z", "outputId": "2b1dd26e-4014-4bf2-8d83-e4851dcdfe3a" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number 1\n", "Number 2\n", "Number 3\n", "Number 4\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 190 } ], "source": [ "map(x -> begin\n", " println(\"Number $x\")\n", " x^2\n", " end, 1:4)" ] }, { "cell_type": "markdown", "metadata": { "id": "FQ5nX-AHIYUr" }, "source": [ "Notice that this syntax allows you to drop the semicolons `;` at the end of each line in the block.\n", "\n", "Yet another way to define an anonymous function is using the `function (args) ... end` syntax:" ] }, { "cell_type": "code", "execution_count": 191, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vKAR1ucxIQzF", "outputId": "3ffb0984-83eb-4731-c7ad-53a8db35abca" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number 1\n", "Number 2\n", "Number 3\n", "Number 4\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 191 } ], "source": [ "map(function (x)\n", " println(\"Number $x\")\n", " x^2\n", " end, 1:4)" ] }, { "cell_type": "markdown", "metadata": { "id": "SGBwAWJ7HQ78" }, "source": [ "Lastly, if you're passing the anonymous function as the first argument to a function (as is the case in this example), it's usually much preferable to define the anonymous function immediately after the function call, using the `do` syntax, like this:" ] }, { "cell_type": "code", "execution_count": 192, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "G60xP30UHW5F", "outputId": "40ecfe2d-e520-416f-d99c-755779bd97b4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number 1\n", "Number 2\n", "Number 3\n", "Number 4\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 192 } ], "source": [ "map(1:4) do x\n", " println(\"Number $x\")\n", " x^2\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "vtsXFsLTJTpD" }, "source": [ "This syntax lets you easily define constructs that feel like language extensions:" ] }, { "cell_type": "code", "execution_count": 193, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "03Hru6aqIIgY", "outputId": "3d2a6e75-39de-4950-9359-31e2273ae36f" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "The square of 1 is 1\n", "The square of 2 is 4\n", "The square of 3 is 9\n", "The square of 4 is 16\n" ] } ], "source": [ "function my_foreach(func, collection)\n", " for i in collection\n", " func(i)\n", " end\n", "end\n", "\n", "my_foreach(1:4) do i\n", " println(\"The square of $i is $(i^2)\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "tFWXrW9z9q8f" }, "source": [ "In fact, Julia has a similar `foreach()` function." ] }, { "cell_type": "markdown", "metadata": { "id": "XXuNr-9iZJf4" }, "source": [ "The `do` syntax could be used to write a Domain Specific Language (DSL), for example an infrastructure automation DSL:" ] }, { "cell_type": "code", "execution_count": 194, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sF1f2HsCZKoT", "outputId": "a373b0b5-7d2b-40df-e037-d4e3377075c9" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Starting web server\n", "Configuring server 1234...\n", "Creating HTML pages on server 1234...\n" ] } ], "source": [ "function spawn_server(startup_func, server_type)\n", " println(\"Starting $server_type server\")\n", " server_id = 1234\n", " println(\"Configuring server $server_id...\")\n", " startup_func(server_id)\n", "end\n", "\n", "# This is the DSL part\n", "spawn_server(\"web\") do server_id\n", " println(\"Creating HTML pages on server $server_id...\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "9jRtJFU82wEO" }, "source": [ "It's also quite nice for event-driven code:" ] }, { "cell_type": "code", "execution_count": 195, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "lU-neWob2_As", "outputId": "6985e9da-f0e0-4d8f-af69-59914ddf3dea" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Mouse clicked at (x = 50, y = 20)\n", "Beep.\n", "Mouse clicked at (x = 120, y = 10)\n", "Beep.\n" ] } ], "source": [ "handlers = []\n", "\n", "on_click(handler) = push!(handlers, handler)\n", "\n", "click(event) = foreach(handlers) do handler handler(event) end\n", "\n", "on_click() do event\n", " println(\"Mouse clicked at $event\")\n", "end\n", "\n", "on_click() do event\n", " println(\"Beep.\")\n", "end\n", "\n", "click((x=50, y=20))\n", "click((x=120, y=10))" ] }, { "cell_type": "markdown", "metadata": { "id": "VclQwKNzajfP" }, "source": [ "It can also be used to create context managers, for example to automatically close an object after it has been used, even if an exception is raised:" ] }, { "cell_type": "code", "execution_count": 196, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "_fCFbtHJarew", "outputId": "ebd17547-b6d3-48ae-9013-4587f1db058b" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Opening connection to database jobs\n", "I'm working with a db object for database jobs\n", "Closing connection to database jobs\n" ] } ], "source": [ "function with_database(func, name)\n", " println(\"Opening connection to database $name\")\n", " db = \"a db object for database $name\"\n", " try\n", " func(db)\n", " finally\n", " println(\"Closing connection to database $name\")\n", " end\n", "end\n", "\n", "with_database(\"jobs\") do db\n", " println(\"I'm working with $db\")\n", " #error(\"Oops\") # try uncommenting this line (database will still be closed)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "D17bqrJwbqS5" }, "source": [ "The equivalent code in Python would look like this:" ] }, { "cell_type": "markdown", "metadata": { "id": "YmCiwD0Ubslm" }, "source": [ "```python\n", "# PYTHON\n", "class Database:\n", " def __init__(self, name):\n", " self.name = name\n", " def __enter__(self):\n", " print(f\"Opening connection to database {self.name}\")\n", " return f\"a db object for database {self.name}\"\n", " def __exit__(self, type, value, traceback):\n", " print(f\"Closing connection to database {self.name}\")\n", "\n", "with Database(\"jobs\") as db:\n", " print(f\"I'm working with {db}\")\n", " #raise Exception(\"Oops\") # try uncommenting this line\n", "```\n", "\n", "Or you could use `contextlib`:\n", "\n", "```python\n", "from contextlib import contextmanager\n", "\n", "@contextmanager\n", "def database(name):\n", " print(f\"Opening connection to database {name}\")\n", " db = f\"a db object for database {name}\"\n", " try:\n", " yield db\n", " finally:\n", " print(f\"Closing connection to database {name}\")\n", "\n", "with database(\"jobs\") as db:\n", " print(f\"I'm working with {db}\")\n", " #raise Exception(\"Oops\") # try uncommenting this line\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "scEsYrSt2jpA" }, "source": [ "## Piping\n", "If you are used to the Object Oriented syntax `\"a b c\".upper().split()`, you may feel that writing `split(uppercase(\"a b c\"))` is a bit backwards. If so, the piping operation `|>` is for you:" ] }, { "cell_type": "code", "execution_count": 197, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wRrmVEWG24_f", "outputId": "274f3a1f-84ad-4d8a-e1ef-4b07b66192a8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{SubString{String}}:\n", " \"A\"\n", " \"B\"\n", " \"C\"" ] }, "metadata": {}, "execution_count": 197 } ], "source": [ "\"a b c\" |> uppercase |> split" ] }, { "cell_type": "markdown", "metadata": { "id": "c2Y1HRhG4S47" }, "source": [ "If you want to pass more than one argument to some of the functions, you can use anonymous functions:" ] }, { "cell_type": "code", "execution_count": 198, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IzIFbaLQ4UCO", "outputId": "a11f6300-dfb0-4678-8029-8889e3dce528" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"A, B and C\"" ] }, "metadata": {}, "execution_count": 198 } ], "source": [ "\"a b c\" |> uppercase |> split |> tokens->join(tokens, \", \", \" and \")" ] }, { "cell_type": "markdown", "metadata": { "id": "GcnKoRyHcGC6" }, "source": [ "The dotted version of the pipe operator works as you might expect, applying the _i_th function of the right array to the _i_th value in the left array:" ] }, { "cell_type": "code", "execution_count": 199, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "a7LytVDKbL_e", "outputId": "630a470f-e142-46eb-dbaa-a7cc45cbca1c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{Real}:\n", " 1.0\n", " 5\n", " 16" ] }, "metadata": {}, "execution_count": 199 } ], "source": [ "[π/2, \"hello\", 4] .|> [sin, length, x->x^2]" ] }, { "cell_type": "markdown", "metadata": { "id": "Czk5ZFOn3LDi" }, "source": [ "## Composition\n", "Julia also lets you compose functions like mathematicians do, using the composition operator ∘ (`\\circ` in the REPL or Jupyter, but not Colab):" ] }, { "cell_type": "code", "execution_count": 200, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iK9UrlF2aU_y", "outputId": "d73a4676-cd32-415f-b340-3c2c2a47c415" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "true" ] }, "metadata": {}, "execution_count": 200 } ], "source": [ "f = exp ∘ sin ∘ sqrt\n", "f(2.0) == exp(sin(sqrt(2.0)))" ] }, { "cell_type": "markdown", "metadata": { "id": "lQJKl9rD-BWj" }, "source": [ "# Methods\n", "Earlier, we discussed structs, which look a lot like Python classes, with instance variables and constructors, but they did not contain any methods (just the inner constructors). In Julia, methods are defined separately, like regular functions:" ] }, { "cell_type": "code", "execution_count": 201, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "axvF_XwJ-DJw", "outputId": "7a33bb6a-1865-4492-fc68-79986c4f8123" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Alice, I am 70 years old.\n" ] } ], "source": [ "struct Person\n", " name\n", " age\n", "end\n", "\n", "function greetings(greeter)\n", " println(\"Hi, my name is $(greeter.name), I am $(greeter.age) years old.\")\n", "end\n", "\n", "p = Person(\"Alice\", 70)\n", "greetings(p)" ] }, { "cell_type": "markdown", "metadata": { "id": "ylOha2mhCRgz" }, "source": [ "Since the `greetings()` method in Julia is not bound to any particular type, we can use it with any other type we want, as long as that type has a `name` and an `age` (i.e., if it quacks like a duck):" ] }, { "cell_type": "code", "execution_count": 202, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vWkQqaEPCgeY", "outputId": "dbb978a3-5d7f-4c7c-cb0d-2cf5fc08b95f" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Auckland, I am 185 years old.\n" ] } ], "source": [ "struct City\n", " name\n", " country\n", " age\n", "end\n", "\n", "using Dates\n", "c = City(\"Auckland\", \"New Zealand\", year(now()) - 1840)\n", "\n", "greetings(c)" ] }, { "cell_type": "markdown", "metadata": { "id": "sRI9XAbrLA0t" }, "source": [ "You could code this the same way in Python if you wanted to:\n", "\n", "```python\n", "# PYTHON\n", "class Person:\n", " def __init__(self, name, age):\n", " self.name = name\n", " self.age = age\n", "\n", "class City:\n", " def __init__(self, name, country, age):\n", " self.name = name\n", " self.country = country\n", " self.age = age\n", "\n", "def greetings(greeter):\n", " print(f\"Hi there, my name is {greeter.name}, I am {greeter.age} years old.\")\n", "\n", "p = Person(\"Lucy\", 70)\n", "greetings(p)\n", "\n", "from datetime import date\n", "c = City(\"Auckland\", \"New Zealand\", date.today().year - 1840)\n", "greetings(c)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "qIcoroYhG5_Y" }, "source": [ "However, many Python programmers would use inheritance in this case:\n", "\n", "```python\n", "class Greeter:\n", " def __init__(self, name, age):\n", " self.name = name\n", " self.age = age\n", " def greetings(self):\n", " print(f\"Hi there, my name is {self.name}, I am {self.age} years old.\")\n", "\n", "class Person(Greeter):\n", " def __init__(self, name, age):\n", " super().__init__(name, age)\n", "\n", "class City(Greeter):\n", " def __init__(self, name, country, age):\n", " super().__init__(name, age)\n", " self.country = country\n", "\n", "p = Person(\"Lucy\", 70)\n", "p.greetings()\n", "\n", "from datetime import date\n", "c = City(\"Auckland\", \"New Zealand\", date.today().year - 1840)\n", "c.greetings()\n", "```\n" ] }, { "cell_type": "markdown", "metadata": { "id": "iRNLWOyURGGp" }, "source": [ "## Extending a Function\n", "One nice thing about having a class hierarchy is that you can override methods in subclasses to get specialized behavior for each class. For example, in Python you could override the `greetings()` method like this:\n", "\n", "```python\n", "# PYTHON\n", "class Developer(Person):\n", " def __init__(self, name, age, language):\n", " super().__init__(name, age)\n", " self.language = language\n", " def greetings(self):\n", " print(f\"Hi there, my name is {self.name}, I am {self.age} years old.\")\n", " print(f\"My favorite language is {self.language}.\")\n", "\n", "d = Developer(\"Amy\", 40, \"Julia\")\n", "d.greetings()\n", "```\n" ] }, { "cell_type": "markdown", "metadata": { "id": "srgbdxLxSWGG" }, "source": [ "Notice that the expression `d.greetings()` will call a different method if `d` is a `Person` or a `Developer`. This is called \"polymorphism\": the same method call behaves differently depending on the type of the object. The language chooses which actual method implementation to call, based on the type of `d`: this is called method \"dispatch\". More specifically, since it only depends on a single variable, it is called \"single dispatch\".\n", "\n", "The good news is that Julia can do single dispatch as well:" ] }, { "cell_type": "code", "execution_count": 203, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "j6fIldepUFA1", "outputId": "792fd7a9-be5c-48c0-9016-4369f01d76ca" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Amy, I am 40 years old.\n", "My favorite language is Julia.\n" ] } ], "source": [ "struct Developer\n", " name\n", " age\n", " language\n", "end\n", "\n", "function greetings(dev::Developer)\n", " println(\"Hi, my name is $(dev.name), I am $(dev.age) years old.\")\n", " println(\"My favorite language is $(dev.language).\")\n", "end\n", "\n", "d = Developer(\"Amy\", 40, \"Julia\")\n", "greetings(d)" ] }, { "cell_type": "markdown", "metadata": { "id": "2ji22KZNVHub" }, "source": [ "Notice that the `dev` argument is followed by `::Developer`, which means that this method will only be called if the argument has that type.\n", "\n", "We have **extended** the `greetings` **function**, so that it now has two different implementations, called **methods**, each for different argument types: namely, `greetings(dev::Developer)` for arguments of type `Developer`, and `greetings(greeter)` for values of any other type.\n", "\n", "You can easily get the list of all the methods of a given function:" ] }, { "cell_type": "code", "execution_count": 204, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 81 }, "id": "6yQgjmjST2xL", "outputId": "4984daed-5497-43c1-c639-8f357fc9c2f2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "# 2 methods for generic function \"greetings\" from \u001b[35mMain\u001b[39m:\n", " [1] greetings(\u001b[90mdev\u001b[39m::\u001b[1mDeveloper\u001b[22m)\n", "\u001b[90m @\u001b[39m \u001b[90m\u001b[4mIn[203]:7\u001b[24m\u001b[39m\n", " [2] greetings(\u001b[90mgreeter\u001b[39m)\n", "\u001b[90m @\u001b[39m \u001b[90m\u001b[4mIn[201]:6\u001b[24m\u001b[39m" ], "text/html": [ "# 2 methods for generic function greetings from \u001b[35mMain\u001b[39m:
  • greetings(dev::Developer) in Main at In[203]:7
  • greetings(greeter) in Main at In[201]:6
" ] }, "metadata": {}, "execution_count": 204 } ], "source": [ "methods(greetings)" ] }, { "cell_type": "markdown", "metadata": { "id": "iS7Byq8yd8uE" }, "source": [ "You can also get the list of all the methods which take a particular type as argument:" ] }, { "cell_type": "code", "execution_count": 205, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 64 }, "id": "ZxFaPzByd0qv", "outputId": "05f256c9-43eb-4cf7-896d-c04f121bcb81" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "[1] greetings(\u001b[90mdev\u001b[39m::\u001b[1mDeveloper\u001b[22m)\u001b[90m @\u001b[39m \u001b[90mMain\u001b[39m \u001b[90m\u001b[4mIn[203]:7\u001b[24m\u001b[39m" ], "text/html": [ "1-element Vector{Method}:
  • greetings(dev::Developer) in Main at In[203]:7
" ] }, "metadata": {}, "execution_count": 205 } ], "source": [ "methodswith(Developer)" ] }, { "cell_type": "markdown", "metadata": { "id": "ab4DP1-fT7dE" }, "source": [ "When you call the `greetings()` function, Julia automatically dispatches the call to the appropriate method, depending on the type of the argument. If Julia can determine at compile time what the type of the argument will be, then it optimizes the compiled code so that there's no choice to be made at runtime. This is called **static dispatch**, and it can significantly speed up the program. If the argument's type can't be determined at compile time, then Julia makes the choice at runtime, just like in Python: this is called **dynamic dispatch**." ] }, { "cell_type": "markdown", "metadata": { "id": "Y7U4rXr2GnFx" }, "source": [ "## Multiple Dispatch\n", "Julia actually looks at the types of _all_ the positional arguments, not just the first one. This is called **multiple dispatch**. For example:" ] }, { "cell_type": "code", "execution_count": 206, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "YE7pb7tHHEcb", "outputId": "f6037db5-e46f-4bbf-ce7b-977d079dc848" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1" ] }, "metadata": {}, "execution_count": 206 } ], "source": [ "multdisp(a::Int64, b::Int64) = 1\n", "multdisp(a::Int64, b::Float64) = 2\n", "multdisp(a::Float64, b::Int64) = 3\n", "multdisp(a::Float64, b::Float64) = 4\n", "\n", "multdisp(10, 20) # try changing the arguments to get each possible output" ] }, { "cell_type": "markdown", "metadata": { "id": "wJtjHEYGJr5B" }, "source": [ "Julia always chooses the most specific method it can, so the following method will only be called if the first argument is neither an `Int64` nor a `Float64`:" ] }, { "cell_type": "code", "execution_count": 207, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "SrZv6JZBKZGF", "outputId": "06ae6e50-6d32-4aba-b762-77a264ded958" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1" ] }, "metadata": {}, "execution_count": 207 } ], "source": [ "multdisp(a::Any, b::Int64) = 5\n", "\n", "multdisp(10, 20)" ] }, { "cell_type": "markdown", "metadata": { "id": "BiwKlUcMKvbr" }, "source": [ "Julia will raise an exception if there is some ambiguity as to which method is the most specific:" ] }, { "cell_type": "code", "execution_count": 208, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wnvxky-JJQxU", "outputId": "8437eeb9-f835-46c2-cf07-5fced33b5dbc" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(Main.ambig, (10, 20), 0x00000000000068b5)" ] }, "metadata": {}, "execution_count": 208 } ], "source": [ "ambig(a::Int64, b) = 1\n", "ambig(a, b::Int64) = 2\n", "\n", "try\n", " ambig(10, 20)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "5Pb7CA7kK8UT" }, "source": [ "To solve this problem, you can explicitely define a method for the ambiguous case:" ] }, { "cell_type": "code", "execution_count": 209, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mlr9q0GxLL-q", "outputId": "ce084b56-5326-4312-d51c-ca968d0e7440" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3" ] }, "metadata": {}, "execution_count": 209 } ], "source": [ "ambig(a::Int64, b::Int64) = 3\n", "ambig(10, 20)" ] }, { "cell_type": "markdown", "metadata": { "id": "W19FsOf2GOcF" }, "source": [ "So you can have polymorphism in Julia, just like in Python. This means that you can write your algorithms in a generic way, without having to know the exact types of the values you are manipulating, and it will work fine, as long as these types act in the general way you expect (i.e., if they \"quack like ducks\"). For example:" ] }, { "cell_type": "code", "execution_count": 210, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "L_i8ksenUoz8", "outputId": "9b117baf-c825-4cf2-a830-6bbc0b7a71a4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Alice, I am 70 years old.\n", "How can I help?\n", "Hi, my name is Amy, I am 40 years old.\n", "My favorite language is Julia.\n", "How can I help?\n" ] } ], "source": [ "function how_can_i_help(greeter)\n", " greetings(greeter)\n", " println(\"How can I help?\")\n", "end\n", "\n", "how_can_i_help(p) # called on a Person\n", "how_can_i_help(d) # called on a Developer" ] }, { "cell_type": "markdown", "metadata": { "id": "jZmax1CvVBKT" }, "source": [ "## Calling `super()`?\n", "You may have noticed that the `greetings(dev::Developer)` method could be improved, since it currently duplicates the implementation of the base method `greetings(greeter)`. In Python, you would get rid of this duplication by calling the base class's `greetings()` method, using `super()`:\n", "\n", "```python\n", "# PYTHON\n", "class Developer(Person):\n", " def __init__(self, name, age, language):\n", " super().__init__(name, age)\n", " self.language = language\n", " def greetings(self):\n", " super().greetings() # <== THIS!\n", " print(f\"My favorite language is {self.language}.\")\n", "\n", "d = Developer(\"Amy\", 40, \"Julia\")\n", "d.greetings()\n", "```\n", "\n", "In Julia, you can do something pretty similar, although you have to implement your own `super()` function, as it is not part of the language:" ] }, { "cell_type": "code", "execution_count": 211, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "8Oz-Xe4VmMRr", "outputId": "fedbebac-a532-4989-882a-758e41e5d869" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Amy, I am 40 years old.\n", "My favorite language is Julia.\n" ] } ], "source": [ "super(dev::Developer) = Person(dev.name, dev.age)\n", "\n", "function greetings(dev::Developer)\n", " greetings(super(dev))\n", " println(\"My favorite language is $(dev.language).\")\n", "end\n", "\n", "greetings(d)" ] }, { "cell_type": "markdown", "metadata": { "id": "WqtmgScLr3oz" }, "source": [ "However, this implementation creates a new `Person` instance when calling `super(dev)`, copying the `name` and `age` fields. That's okay for small objects, but it's not ideal for larger ones. Instead, you can explicitely call the specific method you want by using the `invoke()` function:" ] }, { "cell_type": "code", "execution_count": 212, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XPlSZ5kaqz0l", "outputId": "966caf48-952f-42ec-bad2-3c378960b67b" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi, my name is Amy, I am 40 years old.\n", "My favorite language is Julia.\n" ] } ], "source": [ "function greetings(dev::Developer)\n", " invoke(greetings, Tuple{Any}, dev)\n", " println(\"My favorite language is $(dev.language).\")\n", "end\n", "\n", "greetings(d)" ] }, { "cell_type": "markdown", "metadata": { "id": "Vrf7C_Cgsob2" }, "source": [ "The `invoke()` function expects the following arguments:\n", "* The first argument is the function to call.\n", "* The second argument is the type of the desired method's arguments tuple: `Tuple{TypeArg1, TypeArg2, etc.}`. In this case we want to call the base function, which takes a single `Any` argument (the `Any` type is implicit when no type is specified).\n", "* Lastly, it takes all the arguments to be passed to the method. In this case, there's just one: `dev`." ] }, { "cell_type": "markdown", "metadata": { "id": "emqToYz-ypOS" }, "source": [ "As you can see, we managed to get the same advantages Object-Oriented programming offers, without defining classes or using inheritance. This takes a bit of getting used to, but you might come to prefer this style of generic programming. Indeed, OO programming encourage you to bundle data and behavior together, but this is not always a good idea. Let's look at one example:\n", "\n", "```python\n", "# PYTHON\n", "class Rectangle:\n", " def __init__(self, height, width):\n", " self.height = height\n", " self.width = width\n", " def area(self):\n", " return self.height * self.width\n", "\n", "class Square(Rectangle):\n", " def __init__(self, length):\n", " super().__init__(length, length)\n", "```\n", "\n", "It makes sense for the `Square` class to be a subclass of the `Rectangle` class, since a square **is a** special type of rectangle. It also makes sense for the `Square` class to inherit from all of the `Rectangle` class's behavior, such as the `area()` method. However, it does not really make sense for rectangles and squares to have the same memory representation: a `Rectangle` needs two numbers (`height` and `width`), while a `Square` only needs one (`length`).\n", "\n", "It's possible to work around this issue like this:\n", "\n", "```python\n", "# PYTHON\n", "class Rectangle:\n", " def __init__(self, height, width):\n", " self.height = height\n", " self.width = width\n", " def area(self):\n", " return self.height * self.width\n", "\n", "class Square(Rectangle):\n", " def __init__(self, length):\n", " self.length = length\n", " @property\n", " def width(self):\n", " return self.length\n", " @property\n", " def height(self):\n", " return self.length\n", "```\n", "\n", "That's better: now, each square is only represented using a single number. We've inherited the behavior, but not the data.\n", "\n", "In Julia, you could code this like so:" ] }, { "cell_type": "code", "execution_count": 213, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "G7aHm-s_y92c", "outputId": "c9d6fea4-7aa6-47ed-d3bb-fb3f9670904d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "height (generic function with 2 methods)" ] }, "metadata": {}, "execution_count": 213 } ], "source": [ "struct Rectangle\n", " width\n", " height\n", "end\n", "\n", "width(rect::Rectangle) = rect.width\n", "height(rect::Rectangle) = rect.height\n", "\n", "area(rect) = width(rect) * height(rect)\n", "\n", "struct Square\n", " length\n", "end\n", "\n", "width(sq::Square) = sq.length\n", "height(sq::Square) = sq.length" ] }, { "cell_type": "code", "execution_count": 214, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "1Ck-ML8CuOMm", "outputId": "5d493d3f-9b96-44f8-f7e8-a4f58628fff5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "25" ] }, "metadata": {}, "execution_count": 214 } ], "source": [ "area(Square(5))" ] }, { "cell_type": "markdown", "metadata": { "id": "qUy8eU__-swA" }, "source": [ "Notice that the `area()` function relies on the getters `width()` and `height()`, rather than directly on the fields `width` and `height`. This way, the argument can be of any type at all, as long as it has these getters." ] }, { "cell_type": "markdown", "metadata": { "id": "iPtX04AJkXnI" }, "source": [ "## Abstract Types\n", "One nice thing about the class hierarchy we defined in Python is that it makes it clear that a square **is a** kind of rectangle. Any new function you define that takes a `Rectangle` as an argument will automatically accept a `Square` as well, but no other non-rectangle type. In contrast, our `area()` function currently accepts anything at all.\n", "\n", "In Julia, a concrete type like `Square` cannot extend another concrete type like `Rectangle`. However, any type can extend from an abstract type. Let's define some abstract types to create a type hierarchy for our `Square` and `Rectangle` types." ] }, { "cell_type": "code", "execution_count": 215, "metadata": { "id": "HgXYu8Y7kFf7" }, "outputs": [], "source": [ "abstract type AbstractShape end\n", "abstract type AbstractRectangle <: AbstractShape end # <: means \"subtype of\"\n", "abstract type AbstractSquare <: AbstractRectangle end" ] }, { "cell_type": "markdown", "metadata": { "id": "s9jYhVkntUXo" }, "source": [ "The `<:` operator means \"subtype of\"." ] }, { "cell_type": "markdown", "metadata": { "id": "SFoq_2FUtjix" }, "source": [ "Now we can attach the `area()` function to the `AbstractRectangle` type, instead of any type at all:" ] }, { "cell_type": "code", "execution_count": 216, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DpCEaRNytYNQ", "outputId": "c9e64980-6d5b-4b93-94a9-9094ccebfd3e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "area (generic function with 2 methods)" ] }, "metadata": {}, "execution_count": 216 } ], "source": [ "area(rect::AbstractRectangle) = width(rect) * height(rect)" ] }, { "cell_type": "markdown", "metadata": { "id": "utwPa8jLt7bF" }, "source": [ "Now we can define the concrete types, as subtypes of `AbstractRectangle` and `AbstractSquare`:" ] }, { "cell_type": "code", "execution_count": 217, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cHChkf1Mt0GN", "outputId": "14837b62-0969-4234-880f-31965c4c976b" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "height (generic function with 4 methods)" ] }, "metadata": {}, "execution_count": 217 } ], "source": [ "struct Rectangle_v2 <: AbstractRectangle\n", " width\n", " height\n", "end\n", "\n", "width(rect::Rectangle_v2) = rect.width\n", "height(rect::Rectangle_v2) = rect.height\n", "\n", "struct Square_v2 <: AbstractSquare\n", " length\n", "end\n", "\n", "width(sq::Square_v2) = sq.length\n", "height(sq::Square_v2) = sq.length" ] }, { "cell_type": "markdown", "metadata": { "id": "N6JklHCruNkP" }, "source": [ "In short, the Julian approach to type hierarchies looks like this:\n", "\n", "* Create a hierarchy of abstract types to represent the concepts you want to implement.\n", "* Write functions for these abstract types. Much of your implementation can be coded at that level, manipulating abstract concepts.\n", "* Lastly, create concrete types, and write the methods needed to give them the behavior that is expected by the generic algorithms you wrote." ] }, { "cell_type": "markdown", "metadata": { "id": "GZA6mB9hu3pg" }, "source": [ "This pattern is used everywhere in Julia's standard libraries. For example, here are the supertypes of `Float64` and `Int64`:" ] }, { "cell_type": "code", "execution_count": 218, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "No9X1dLtu-nj", "outputId": "97a8b5df-5cfc-4195-cb68-2e52c46da049" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Float64 <: AbstractFloat <: Real <: Number <: Any" ] } ], "source": [ "Base.show_supertypes(Float64)" ] }, { "cell_type": "code", "execution_count": 219, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "RowhPCR_R-QG", "outputId": "aa9d1174-84c5-4e34-f627-bcf61ad49730" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Int64 <: Signed <: Integer <: Real <: Number <: Any" ] } ], "source": [ "Base.show_supertypes(Int64)" ] }, { "cell_type": "markdown", "metadata": { "id": "fTFddfq0CEyy" }, "source": [ "Note: Julia implicitly runs `using Core` and `using Base` when starting the REPL. However, the `show_supertypes()` function is not exported by the `Base` module, thus you cannot access it by just typing `show_supertypes(Float64)`. Instead, you have to specify the module name: `Base.show_supertypes(Float64)`." ] }, { "cell_type": "markdown", "metadata": { "id": "K-q4QCgI-rWI" }, "source": [ "And here is the whole hierarchy of `Number` types:" ] }, { "cell_type": "code", "execution_count": 220, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6Gtn6IB0vQZv", "outputId": "b0ac2e0c-618a-472b-a9e7-0590d6e37f73" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Number\n", " Base.MultiplicativeInverses.MultiplicativeInverse\n", " Base.MultiplicativeInverses.SignedMultiplicativeInverse\n", " Base.MultiplicativeInverses.UnsignedMultiplicativeInverse\n", " Complex\n", " Real\n", " AbstractFloat\n", " BigFloat\n", " Core.BFloat16\n", " Float16\n", " Float32\n", " Float64\n", " AbstractIrrational\n", " Irrational\n", " FixedPointNumbers.FixedPoint\n", " FixedPointNumbers.Fixed\n", " FixedPointNumbers.Normed\n", " Integer\n", " Bool\n", " Signed\n", " BigInt\n", " Int128\n", " Int16\n", " Int32\n", " Int64\n", " Int8\n", " Unsigned\n", " UInt128\n", " UInt16\n", " UInt32\n", " UInt64\n", " UInt8\n", " Rational\n" ] } ], "source": [ "function show_hierarchy(root, indent=0)\n", " println(repeat(\" \", indent * 4), root)\n", " for subtype in subtypes(root)\n", " show_hierarchy(subtype, indent + 1)\n", " end\n", "end\n", "\n", "show_hierarchy(Number)" ] }, { "cell_type": "markdown", "metadata": { "id": "NdE0eLaKf_tM" }, "source": [ "## Iterator Interface\n", "You will sometimes want to provide a way to iterate over your custom types. In Python, this requires defining the `__iter__()` method which should return an object which implements the `__next__()` method. In Julia, you must define at least two functions:\n", "* `iterate(::YourIteratorType)`, which must return either `nothing` if there are no values in the sequence, or `(first_value, iterator_state)`.\n", "* `iterate(::YourIteratorType, state)`, which must return either `nothing` if there are no more values, or `(next_value, next_iterator_state)`.\n", "\n", "For example, let's create a simple iterator for the Fibonacci sequence:" ] }, { "cell_type": "code", "execution_count": 221, "metadata": { "id": "baemCHqZde4Y" }, "outputs": [], "source": [ "struct FibonacciIterator end" ] }, { "cell_type": "code", "execution_count": 222, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yidgsJt9eBSk", "outputId": "8c4b0987-ddd0-4468-c6df-8fb7cffad5d1" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "iterate (generic function with 273 methods)" ] }, "metadata": {}, "execution_count": 222 } ], "source": [ "import Base.iterate\n", "\n", "iterate(f::FibonacciIterator) = (1, (1, 1))\n", "\n", "function iterate(f::FibonacciIterator, state)\n", " new_state = (state[2], state[1] + state[2])\n", " (new_state[1], new_state)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "9CORRxR8hYrl" }, "source": [ "Now we can iterate over a `FibonacciIterator` instance:" ] }, { "cell_type": "code", "execution_count": 223, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5W0QbY41fJGE", "outputId": "5734816e-0e2f-49e3-c0b1-224ab6c7e0fe" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n" ] } ], "source": [ "for f in FibonacciIterator()\n", " println(f)\n", " f > 10 && break\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "RjEk9nRIhmwV" }, "source": [ "## Indexing Interface\n", "You can also create a type that will be indexable like an array (allowing syntax like `a[5] = 3`). In Python, this requires implementing the `__getitem__()` and `__setitem__()` methods. In Julia, you must implement the `getindex(::YourType, i)`, `setindex!(::YourType, v, i)`, `firstindex(::YourType)` and `lastindex(::YourType)` methods." ] }, { "cell_type": "code", "execution_count": 224, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "i-r1StLujBEL", "outputId": "0d0e9187-ae04-487d-b8e0-2fee55cc3125" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "100" ] }, "metadata": {}, "execution_count": 224 } ], "source": [ "import Base.getindex, Base.firstindex\n", "\n", "struct MySquares end\n", "\n", "getindex(::MySquares, i) = i^2\n", "firstindex(::MySquares) = 0\n", "\n", "S = MySquares()\n", "S[10]" ] }, { "cell_type": "code", "execution_count": 225, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "p8zfJLvDjpSP", "outputId": "58d1013d-d918-482e-adf8-905b697b38e5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "0" ] }, "metadata": {}, "execution_count": 225 } ], "source": [ "S[begin]" ] }, { "cell_type": "code", "execution_count": 226, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "n9y4Y2r2jzTG", "outputId": "fd68bf37-f23e-4bb9-cfde-f7f9714a84d1" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "getindex (generic function with 221 methods)" ] }, "metadata": {}, "execution_count": 226 } ], "source": [ "getindex(S::MySquares, r::UnitRange) = [S[i] for i in r]" ] }, { "cell_type": "code", "execution_count": 227, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vk-gqI08jrQo", "outputId": "13b6092c-869b-4217-e1ee-923b460bcfda" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 4\n", " 9\n", " 16" ] }, "metadata": {}, "execution_count": 227 } ], "source": [ "S[1:4]" ] }, { "cell_type": "markdown", "metadata": { "id": "1j3qxO4TkoVm" }, "source": [ "For more details on these interfaces, and to learn how to build full-blown array types with broadcasting and more, check out [this page](https://docs.julialang.org/en/v1/manual/interfaces/)." ] }, { "cell_type": "markdown", "metadata": { "id": "bD9gzH2XDlGL" }, "source": [ "## Creating a Number Type\n", "Let's create a `MyRational` struct and try to make it mimic the built-in `Rational` type:" ] }, { "cell_type": "code", "execution_count": 228, "metadata": { "id": "nwEtMyR1PB3C" }, "outputs": [], "source": [ "struct MyRational <: Real\n", " num # numerator\n", " den # denominator\n", "end" ] }, { "cell_type": "code", "execution_count": 229, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cwCpLCW6PePs", "outputId": "e0d50f54-806e-440c-fe66-486b15e6344e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational(2, 3)" ] }, "metadata": {}, "execution_count": 229 } ], "source": [ "MyRational(2, 3)" ] }, { "cell_type": "markdown", "metadata": { "id": "vbIz77VzST24" }, "source": [ "It would be more convenient and readable if we could type `2 ⨸ 3` to create a `MyRational`:" ] }, { "cell_type": "code", "execution_count": 230, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VuitXq8eTL4G", "outputId": "3f26ca4d-f22a-41bb-87de-e7020a204bf4" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "⨸ (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 230 } ], "source": [ "function ⨸(num, den)\n", " MyRational(num, den)\n", "end" ] }, { "cell_type": "code", "execution_count": 231, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "isQcJMAeTRi-", "outputId": "633c04ee-da93-4134-bd5e-ea5cb8e0ff7d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational(2, 3)" ] }, "metadata": {}, "execution_count": 231 } ], "source": [ "2 ⨸ 3" ] }, { "cell_type": "markdown", "metadata": { "id": "g594gvCpTTYB" }, "source": [ "I chose `⨸` because it's a symbol that Julia's parser treats as a binary operator, but which is otherwise not used by Julia (see the full [list of parsed symbols](https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm) and their priorities). This particular symbol will have the same priority as multiplication and division." ] }, { "cell_type": "markdown", "metadata": { "id": "3DKaUnqsUG0L" }, "source": [ "If you want to know how to type it and check that it is unused, type `?⨸` (copy/paste the symbol):" ] }, { "cell_type": "code", "execution_count": 232, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 194 }, "id": "km3KccbsUTnQ", "outputId": "3c4d48ef-e1db-40b2-b9f2-69ec7bbc571c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\"\u001b[36m⨸\u001b[39m\" can be typed by \u001b[36m\\odiv\u001b[39m\n", "\n", "search: \u001b[0m\u001b[1m⨸\u001b[22m\n", "\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ " No documentation found for private symbol.\n", "\n", " \u001b[36m⨸\u001b[39m is a \u001b[36mFunction\u001b[39m.\n", "\n", "\u001b[36m # 1 method for generic function \"⨸\" from Main:\u001b[39m\n", "\u001b[36m [1] ⨸(num, den)\u001b[39m\n", "\u001b[36m @ In[230]:1\u001b[39m" ], "text/markdown": "No documentation found for private symbol.\n\n`⨸` is a `Function`.\n\n```\n# 1 method for generic function \"⨸\" from Main:\n [1] ⨸(num, den)\n @ In[230]:1\n```\n", "text/latex": "No documentation found for private symbol.\n\n\\texttt{⨸} is a \\texttt{Function}.\n\n\\begin{verbatim}\n# 1 method for generic function \"⨸\" from Main:\n [1] ⨸(num, den)\n @ In[230]:1\n\\end{verbatim}\n" }, "metadata": {}, "execution_count": 232 } ], "source": [ "?⨸" ] }, { "cell_type": "markdown", "metadata": { "id": "eWzNstBqQckD" }, "source": [ "Now let's make it possible to add two `MyRational` values. We want it to be possible for our `MyRational` type to be used in existing algorithms which rely on `+`, so we must create a new method for the `Base.+` function:" ] }, { "cell_type": "code", "execution_count": 233, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2T7MyjE2RhUW", "outputId": "323ccfe2-23b0-47a6-9b53-3e74b3a6ffd8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "+ (generic function with 205 methods)" ] }, "metadata": {}, "execution_count": 233 } ], "source": [ "import Base.+\n", "\n", "function +(r1::MyRational, r2::MyRational)\n", " (r1.num * r2.den + r1.den * r2.num) ⨸ (r1.den * r2.den)\n", "end" ] }, { "cell_type": "code", "execution_count": 234, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "aFtoPHZRSNoU", "outputId": "787faab6-b9b7-493f-ef24-b1de48373771" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational(19, 15)" ] }, "metadata": {}, "execution_count": 234 } ], "source": [ "2 ⨸ 3 + 3 ⨸ 5" ] }, { "cell_type": "markdown", "metadata": { "id": "8--bQ8izR7_I" }, "source": [ "It's important to import `Base.+` first, or else you would just be defining a new `+` function in the current module (`Main`), which would not be called by existing algorithms." ] }, { "cell_type": "markdown", "metadata": { "id": "x79nFEHQYFfU" }, "source": [ "You can easily implement `*`, `^` and so on, in much the same way." ] }, { "cell_type": "markdown", "metadata": { "id": "GGTgc07sSR4w" }, "source": [ "Let's change the way `MyRational` values are printed, to make them look a bit nicer. For this, we must create a new method for the `Base.show(io::IO, x)` function:" ] }, { "cell_type": "code", "execution_count": 235, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KQN5IqLxVzLF", "outputId": "ea2b177a-a5b2-45a4-ad6b-d2dc40f97940" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "19 ⨸ 15" ] }, "metadata": {}, "execution_count": 235 } ], "source": [ "import Base.show\n", "\n", "function show(io::IO, r::MyRational)\n", " print(io, \"$(r.num) ⨸ $(r.den)\")\n", "end\n", "\n", "2 ⨸ 3 + 3 ⨸ 5" ] }, { "cell_type": "markdown", "metadata": { "id": "rJg_-ZYOXS4m" }, "source": [ "We can expand the `show()` function so it can provide an HTML representation for `MyRational` values. This will be called by the `display()` function in Jupyter or Colab:" ] }, { "cell_type": "code", "execution_count": 236, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 40 }, "id": "h6smoUzBWkNB", "outputId": "9d3b4c45-80d8-43a0-8e38-d03f2249f65d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "19 ⨸ 15" ], "text/html": [ "1915" ] }, "metadata": {}, "execution_count": 236 } ], "source": [ "function show(io::IO, ::MIME\"text/html\", r::MyRational)\n", " print(io, \"$(r.num)$(r.den)\")\n", "end\n", "\n", "2 ⨸ 3 + 3 ⨸ 5" ] }, { "cell_type": "markdown", "metadata": { "id": "5bOKggSjX7hM" }, "source": [ "Next, we want to be able to perform any operation involving `MyRational` values and values of other `Number` types. For example, we may want to multiply integers and `MyRational` values. One option is to define a new method like this:" ] }, { "cell_type": "code", "execution_count": 237, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 40 }, "id": "dlW_pbUPZL_0", "outputId": "de0ec512-737a-497d-d0dd-e12b6a3ee627" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "10 ⨸ 3" ], "text/html": [ "103" ] }, "metadata": {}, "execution_count": 237 } ], "source": [ "import Base.*\n", "\n", "function *(r::MyRational, i::Integer)\n", " (r.num * i) ⨸ r.den\n", "end\n", "\n", "2 ⨸ 3 * 5" ] }, { "cell_type": "markdown", "metadata": { "id": "3Xr--hB-ZlDm" }, "source": [ "Since multiplication is commutative, we need the reverse method as well:" ] }, { "cell_type": "code", "execution_count": 238, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 40 }, "id": "3DkeVGqaZtNv", "outputId": "a1760181-9580-4f25-9027-642a7b358807" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "10 ⨸ 3" ], "text/html": [ "103" ] }, "metadata": {}, "execution_count": 238 } ], "source": [ "function *(i::Integer, r::MyRational)\n", " r * i # this will call the previous method\n", "end\n", "\n", "5 * (2 ⨸ 3) # we need the parentheses since * and ⨸ have the same priority" ] }, { "cell_type": "markdown", "metadata": { "id": "hzW07JkXggLo" }, "source": [ "It's cumbersome to have to define these methods for every operation. There's a better way, which we will explore in the next two sections." ] }, { "cell_type": "markdown", "metadata": { "id": "TWXZrND8N-nR" }, "source": [ "## Conversion\n", "It is possible to provide a way for integers to be automatically converted to `MyRational` values:" ] }, { "cell_type": "code", "execution_count": 239, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 40 }, "id": "SFIenX2Og0qY", "outputId": "3bfa87cc-18eb-4438-dc82-57681c55b0c5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "42 ⨸ 1" ], "text/html": [ "421" ] }, "metadata": {}, "execution_count": 239 } ], "source": [ "import Base.convert\n", "\n", "MyRational(x::Integer) = MyRational(x, 1)\n", "\n", "convert(::Type{MyRational}, x::Integer) = MyRational(x)\n", "\n", "convert(MyRational, 42)" ] }, { "cell_type": "markdown", "metadata": { "id": "n_gGRgRSwrkS" }, "source": [ "The `Type{MyRational}` type is a special type which has a single instance: the `MyRational` type itself. So this `convert()` method only accepts `MyRational` itself as its first argument (and we don't actually use the first argument, so we don't even need to give it a name in the function declaration)." ] }, { "cell_type": "markdown", "metadata": { "id": "5B9cNbV5lbHm" }, "source": [ "Now integers will be automatically converted to `MyRational` values when you assign them to an array whose element type if `MyRational`:" ] }, { "cell_type": "code", "execution_count": 240, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "YWdijh_2k7LU", "outputId": "eff3e8a2-0798-4cec-d46f-d0d46209d333" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "MyRational[5 ⨸ 1, 6 ⨸ 1]\n" ] } ], "source": [ "a = [2 ⨸ 3] # the element type is MyRational\n", "a[1] = 5 # convert(MyRational, 5) is called automatically\n", "push!(a, 6) # convert(MyRational, 6) is called automatically\n", "println(a)" ] }, { "cell_type": "markdown", "metadata": { "id": "ESWblH5olvOY" }, "source": [ "Conversion will also occur automatically in these cases:\n", "* `r::MyRational = 42`: assigning an integer to `r` where `r` is a local variable with a declared type of `MyRational`.\n", "* `s.b = 42` if `s` is a struct and `b` is a field of type `MyRational` (also when calling `new(42)` on that struct, assuming `b` is the first field).\n", "* `return 42` if the return type is declared as `MyRational` (e.g., `function f(x)::MyRational ... end`)." ] }, { "cell_type": "markdown", "metadata": { "id": "kIulkBomd5hy" }, "source": [ "However, there is no automatic conversion when calling functions:" ] }, { "cell_type": "code", "execution_count": 241, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7Jf6rT8uhAtU", "outputId": "0892f80e-3ca3-47bd-ee0e-84428bee3599" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MethodError(Main.for_my_rationals_only, (42,), 0x00000000000068dc)" ] }, "metadata": {}, "execution_count": 241 } ], "source": [ "function for_my_rationals_only(x::MyRational)\n", " println(\"It works:\", x)\n", "end\n", "\n", "try\n", " for_my_rationals_only(42)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "KhtxelSROzU_" }, "source": [ "## Promotion\n", "The `Base` functions `+`, `-`, `*`, `/`, `^`, etc. all use a \"promotion\" algorithm to convert the arguments to the appropriate type. For example, adding an integer and a float promotes the integer to a float before the addition takes place. These functions use the `promote()` function for this. For example, given several integers and a float, all integers get promoted to floats:" ] }, { "cell_type": "code", "execution_count": 242, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BEa8TRPuokYN", "outputId": "54a1532c-003c-408e-af7b-2266ebc0b479" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(1.0, 2.0, 3.0, 4.0)" ] }, "metadata": {}, "execution_count": 242 } ], "source": [ "promote(1, 2, 3, 4.0)" ] }, { "cell_type": "markdown", "metadata": { "id": "wDpLvwpMPP59" }, "source": [ "This is why a sum of integers and floats results in a float:" ] }, { "cell_type": "code", "execution_count": 243, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "grWU-hHHPI7v", "outputId": "d6efdc25-d1d6-4d36-8deb-ba268396f276" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "10.0" ] }, "metadata": {}, "execution_count": 243 } ], "source": [ "1 + 2 + 3 + 4.0" ] }, { "cell_type": "markdown", "metadata": { "id": "pkrDanbRO7b2" }, "source": [ "The `promote()` function is also called when creating an array. For example, the following array is a `Float64` array:" ] }, { "cell_type": "code", "execution_count": 244, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "AeDsWTZjO-4d", "outputId": "8e6ce4b8-66bb-4330-8aa0-c379c265aa51" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Float64}:\n", " 1.0\n", " 2.0\n", " 3.0\n", " 4.0" ] }, "metadata": {}, "execution_count": 244 } ], "source": [ "a = [1, 2, 3, 4.0]" ] }, { "cell_type": "markdown", "metadata": { "id": "JCdZ4NhMo505" }, "source": [ "What about the `MyRational` type? Rather than create new methods for the `promote()` function, the recommended approach is to create a new method for the `promote_rule()` function. It takes two types and returns the type to convert to:" ] }, { "cell_type": "code", "execution_count": 245, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3xJTjr7XPgtx", "outputId": "bcf5bab0-896a-4d08-f1a4-9ee7b5395d88" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Float64" ] }, "metadata": {}, "execution_count": 245 } ], "source": [ "promote_rule(Float64, Int64)" ] }, { "cell_type": "markdown", "metadata": { "id": "W68U2_SjPt_T" }, "source": [ "Let's implement a new method for this function, to make sure that any subtype of the `Integer` type will be promoted to `MyRational`:" ] }, { "cell_type": "code", "execution_count": 246, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wjK9_3Bierna", "outputId": "0ffcefda-1a5f-4ff6-edf1-dd0ced8602e7" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "promote_rule (generic function with 153 methods)" ] }, "metadata": {}, "execution_count": 246 } ], "source": [ "import Base.promote_rule\n", "\n", "promote_rule(::Type{MyRational}, ::Type{T}) where {T <: Integer} = MyRational" ] }, { "cell_type": "markdown", "metadata": { "id": "OSXo1GaxwU9F" }, "source": [ "This method definition uses **parametric types**: the type `T` can be any type at all, as long as it is a subtype of the `Integer` abstract type. If you tried to define the method `promote_rule(::Type{MyRational}, ::Type{Integer})`, it would expect the type `Integer` itself as the second argument, which would not work, since the `promote_rule()` function will usually be called with concrete types like `Int64` as its arguments." ] }, { "cell_type": "markdown", "metadata": { "id": "Cn7m2aTTrC6X" }, "source": [ "Let's check that it works:" ] }, { "cell_type": "code", "execution_count": 247, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bD7p0DHFfaCg", "outputId": "d3918d2d-360b-469f-de5b-995a2c984854" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(5 ⨸ 1, 2 ⨸ 3)" ] }, "metadata": {}, "execution_count": 247 } ], "source": [ "promote(5, 2 ⨸ 3)" ] }, { "cell_type": "markdown", "metadata": { "id": "WVRYV79ErH81" }, "source": [ "Yep! Now whenever we call `+`, `-`, etc., with an integer and a `MyRational` value, the integer will get automatically promoted to a `MyRational` value:" ] }, { "cell_type": "code", "execution_count": 248, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 40 }, "id": "lmjjo9lSegy9", "outputId": "15692199-61af-488a-f6b7-5deb966922d0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "17 ⨸ 3" ], "text/html": [ "173" ] }, "metadata": {}, "execution_count": 248 } ], "source": [ "5 + 2 ⨸ 3" ] }, { "cell_type": "markdown", "metadata": { "id": "B6jVZiYXxuPJ" }, "source": [ "Under the hood:\n", "* this called `+(5, 2 ⨸ 3)`,\n", " * which called the `+(::Number, ::Number)` method (thanks to multiple dispatch),\n", " * which called `promote(5, 2 ⨸ 3)`,\n", " * which called `promote_rule(Int64, MyRational)`,\n", " * which called `promote_rule(::MyRational, ::T) where {T <: Integer}`,\n", " * which returned `MyRational`,\n", " * then the `+(::Number, ::Number)` method called `convert(MyRational, 5)`,\n", " * which called `MyRational(5)`,\n", " * which returned `MyRational(5, 1)`,\n", " * and finally `+(::Number, ::Number)` called `+(MyRational(5, 1), MyRational(2, 3))`,\n", " * which returned `MyRational(17, 3)`." ] }, { "cell_type": "markdown", "metadata": { "id": "d30Q7aANrik5" }, "source": [ "The benefit of this approach is that we only need to implement the `+`, `-`, etc. functions for pairs of `MyRational` values, not with all combinations of `MyRational` values and integers." ] }, { "cell_type": "markdown", "metadata": { "id": "GftnTFTyLchM" }, "source": [ "If your head hurts, it's perfectly normal. 😉 Writing a new type that is easy to use, flexible and plays nicely with existing types takes a bit of planning and work, but the point is that you will not write these every day, and once you have, they will make your life much easier." ] }, { "cell_type": "markdown", "metadata": { "id": "GSVv5YNisaxJ" }, "source": [ "Now let's handle the case where we want to execute operations with `MyRational` values and floats. In this case, we naturally want to promote the `MyRational` value to a float. We first need to define how to convert a `MyRational` value to any subtype of `AbstractFloat`:" ] }, { "cell_type": "code", "execution_count": 249, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DZSFfORzrx4I", "outputId": "9bab206b-5d32-4046-b68d-384cc4f26ad9" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "convert (generic function with 273 methods)" ] }, "metadata": {}, "execution_count": 249 } ], "source": [ "convert(::Type{T}, x::MyRational) where {T <: AbstractFloat} = T(x.num / x.den)" ] }, { "cell_type": "markdown", "metadata": { "id": "XC5z7YxcMjJN" }, "source": [ "This `convert()` works with any type `T` which is a subtype of `AbstractFloat`. It just computes `x.num / x.den` and converts the result to type `T`. Let's try it:" ] }, { "cell_type": "code", "execution_count": 250, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LxPjD04VMBXg", "outputId": "b629ab95-2f43-43ac-8cc7-8c56e79741fe" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.5" ] }, "metadata": {}, "execution_count": 250 } ], "source": [ "convert(Float64, 3 ⨸ 2)" ] }, { "cell_type": "markdown", "metadata": { "id": "Iity08_TMyLa" }, "source": [ "Now let's define a `promote_rule()` method which will work for any type `T` which is a subtype of `AbstractFloat`, and which will give priority to `T` over `MyRational`:" ] }, { "cell_type": "code", "execution_count": 251, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "S2xlgBf8MGa8", "outputId": "08a05284-a41b-4123-aa6e-3e90f8a1da83" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "promote_rule (generic function with 154 methods)" ] }, "metadata": {}, "execution_count": 251 } ], "source": [ "promote_rule(::Type{MyRational}, ::Type{T}) where {T <: AbstractFloat} = T" ] }, { "cell_type": "code", "execution_count": 252, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5Xe9_k_GQZw0", "outputId": "c3629e79-6166-4684-a30b-204e9e79a872" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(0.5, 4.0)" ] }, "metadata": {}, "execution_count": 252 } ], "source": [ "promote(1 ⨸ 2, 4.0)" ] }, { "cell_type": "markdown", "metadata": { "id": "ZRQrziIuNONH" }, "source": [ "Now we can combine floats and `MyRational` values easily:" ] }, { "cell_type": "code", "execution_count": 253, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ncZJSLjTsMsG", "outputId": "ccf9f49c-94fe-43f1-ed51-8929f14b4606" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.5" ] }, "metadata": {}, "execution_count": 253 } ], "source": [ "2.25 ^ (1 ⨸ 2)" ] }, { "cell_type": "markdown", "metadata": { "id": "p6msWOnrRwTO" }, "source": [ "## Parametric Types and Functions" ] }, { "cell_type": "markdown", "metadata": { "id": "6aNBefNARChe" }, "source": [ "Julia's `Rational` type is actually a **parametric type** which ensures that the numerator and denominator have the same type `T`, subtype of `Integer`. Here's a new version of our rational struct which enforces the same constraint:" ] }, { "cell_type": "code", "execution_count": 254, "metadata": { "id": "I0Vyf_ogRdy0" }, "outputs": [], "source": [ "struct MyRational2{T <: Integer}\n", " num::T\n", " den::T\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "SHdhiFdlSwOd" }, "source": [ "To instantiate this type, we can specify the type `T`:" ] }, { "cell_type": "code", "execution_count": 255, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FTfSXjQ4RseZ", "outputId": "7ffaa2c8-99ce-4576-ea61-173ece9b3bac" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational2{BigInt}(2, 3)" ] }, "metadata": {}, "execution_count": 255 } ], "source": [ "MyRational2{BigInt}(2, 3)" ] }, { "cell_type": "markdown", "metadata": { "id": "_dFuAINiTboo" }, "source": [ "Alternatively, we can use the `MyRational2` type's default constructor, with two integers of the same type:" ] }, { "cell_type": "code", "execution_count": 256, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "8Q8RWIB8Toq7", "outputId": "9048b0c3-88e7-4270-f31c-234dc60ca5eb" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational2{Int64}(2, 3)" ] }, "metadata": {}, "execution_count": 256 } ], "source": [ "MyRational2(2, 3)" ] }, { "cell_type": "markdown", "metadata": { "id": "8hhYRFcWXKkG" }, "source": [ "If we want to be able to construct a `MyRational2` with integers of different types, we must write an appropriate constructor which handles the promotion rule:" ] }, { "cell_type": "code", "execution_count": 257, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BdRaOp44XS-I", "outputId": "c6c06bd9-b858-492d-f3cd-ed7646c249c0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational2" ] }, "metadata": {}, "execution_count": 257 } ], "source": [ "function MyRational2(num::Integer, den::Integer)\n", " MyRational2(promote(num, den)...)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "F1-HWooSXwRj" }, "source": [ "This constructor accepts two integers of potentially different types, and promotes them to the same type. Then it calls the default `MyRational2` constructor which expects two arguments of the same type. The syntax `f(args...)` is analog to Python's `f(*args)`." ] }, { "cell_type": "markdown", "metadata": { "id": "ui3Oe7CHYhKs" }, "source": [ "Let's see if this works:" ] }, { "cell_type": "code", "execution_count": 258, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "55PS-B4kXsMv", "outputId": "83034d6a-9fc5-4965-df9e-943ab8287f84" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "MyRational2{BigInt}(2, 3)" ] }, "metadata": {}, "execution_count": 258 } ], "source": [ "MyRational2(2, BigInt(3))" ] }, { "cell_type": "markdown", "metadata": { "id": "FYBh3sMgXvIy" }, "source": [ "Great!" ] }, { "cell_type": "markdown", "metadata": { "id": "TFB6iS8cYuwk" }, "source": [ "Note that all parametrized types such as `MyRational2{Int64}` or `MyRational2{BigInt}` are subtypes of `MyRational2`. So if a function accepts a `MyRational2` argument, you can pass it an instance of any specific, parametrized type:" ] }, { "cell_type": "code", "execution_count": 259, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Xoe56vbfZXr8", "outputId": "7020dc5c-9e9c-4ca1-af51-30ed3c47d75c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "MyRational2{BigInt}(1, 2)\n", "MyRational2{Int64}(1, 2)\n" ] } ], "source": [ "function for_any_my_rational2(x::MyRational2)\n", " println(x)\n", "end\n", "\n", "for_any_my_rational2(MyRational2{BigInt}(1, 2))\n", "for_any_my_rational2(MyRational2{Int64}(1, 2))" ] }, { "cell_type": "markdown", "metadata": { "id": "KK02R7mAazjo" }, "source": [ "A more explicit (but verbose) syntax for this function is:" ] }, { "cell_type": "code", "execution_count": 260, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "w9wLF0uYa377", "outputId": "cbd4fabe-5d9c-424b-8beb-219150c8848d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "for_any_my_rational2 (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 260 } ], "source": [ "function for_any_my_rational2(x::MyRational2{T}) where {T <: Integer}\n", " println(x)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "HzrkuhIMT3rq" }, "source": [ "It's useful to think of types as sets. For example, the `Int64` type represents the set of all 64-bit integer values, so `42 isa Int64`:\n", "* When `x` is an instance of some type `T`, it is an element of the set `T` represents, and `x isa T`.\n", "* When `U` is a subtype of `V`, `U` is a subset of `V`, and `U <: V`.\n", "\n", "The `MyRational2` type itself (without any parameter) represents the set of all values of `MyRational2{T}` for all subtypes `T` of `Integer`. In other words, it is the union of all the `MyRational2{T}` types. This is called a `UnionAll` type, and indeed the type `MyRational2` itself is an instance of the `UnionAll` type:" ] }, { "cell_type": "code", "execution_count": 261, "metadata": { "id": "hGKucwR4Z78q" }, "outputs": [], "source": [ "@assert MyRational2{BigInt}(2, 3) isa MyRational2{BigInt}\n", "@assert MyRational2{BigInt}(2, 3) isa MyRational2\n", "@assert MyRational2 === (MyRational2{T} where {T <: Integer})\n", "@assert MyRational2{BigInt} <: MyRational2\n", "@assert MyRational2 isa UnionAll" ] }, { "cell_type": "markdown", "metadata": { "id": "Tv483WelU4It" }, "source": [ "If we dump the `MyRational2` type, we can see that it is a `UnionAll` instance, with a parameter type `T`, constrained to a subtype of the `Integer` type (since the upper bound `ub` is `Integer`):" ] }, { "cell_type": "code", "execution_count": 262, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6Xzox_pwU1AA", "outputId": "fa95f578-8d90-426f-959b-fff8f2d16524" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "UnionAll\n", " var: TypeVar\n", " name: Symbol T\n", " lb: Union{}\n", " ub: Integer <: Real\n", " body: MyRational2{T<:Integer} <: Any\n", " num::T\n", " den::T\n" ] } ], "source": [ "dump(MyRational2)" ] }, { "cell_type": "markdown", "metadata": { "id": "eEpPNFJBcoag" }, "source": [ "There's a lot more to learn about Julia types. When you feel ready to explore this in more depth, check out [this page](https://docs.julialang.org/en/v1.4/manual/types/). You can also take a look at the [source code of Julia's rationals](https://github.com/JuliaLang/julia/blob/master/base/rational.jl)." ] }, { "cell_type": "markdown", "metadata": { "id": "KmT7ZJEMJ2Vv" }, "source": [ "# Writing/Reading Files\n", "The `do` syntax we saw earlier is helpful when using the `open()` function:\n" ] }, { "cell_type": "code", "execution_count": 263, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oGW3LMvkJ6JM", "outputId": "3e012b03-ea26-4d17-d2c5-0f9351c654d9" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[This is a test.]\n", "[I repeat, this is a test.]\n" ] } ], "source": [ "open(\"test.txt\", \"w\") do f\n", " write(f, \"This is a test.\\n\")\n", " write(f, \"I repeat, this is a test.\\n\")\n", "end\n", "\n", "open(\"test.txt\") do f\n", " for line in eachline(f)\n", " println(\"[$line]\")\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "f6XIenvgKdzR" }, "source": [ "The `open()` function automatically closes the file at the end of the block. Notice that the line feeds `\\n` at the end of each line are not returned by the `eachline()` function. So the equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "with open(\"test.txt\", \"w\") as f:\n", " f.write(\"This is a test.\\n\")\n", " f.write(\"I repeat, this is a test.\\n\")\n", "\n", "with open(\"test.txt\") as f:\n", " for line in f.readlines():\n", " line = line.rstrip(\"\\n\")\n", " print(f\"[{line}]\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "SQg2lDpRW_2R" }, "source": [ "Alternatively, you can read the whole file into a string:" ] }, { "cell_type": "code", "execution_count": 264, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FKRtde6dXDpe", "outputId": "878e7153-aecd-4950-d8cc-222a8497b4bb" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"This is a test.\\nI repeat, this is a test.\\n\"" ] }, "metadata": {}, "execution_count": 264 } ], "source": [ "open(\"test.txt\") do f\n", " s = read(f, String)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "5vxOQnwucbIW" }, "source": [ "Or more concisely:" ] }, { "cell_type": "code", "execution_count": 265, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2s-SRVw7cc5G", "outputId": "ac8ab767-57ac-41dd-f53e-2f382bee7589" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"This is a test.\\nI repeat, this is a test.\\n\"" ] }, "metadata": {}, "execution_count": 265 } ], "source": [ "s = read(\"test.txt\", String)" ] }, { "cell_type": "markdown", "metadata": { "id": "dHb_f2lWXM0Z" }, "source": [ "The Python equivalent is:\n", "\n", "```python\n", "# PYTHON\n", "with open(\"test.txt\") as f:\n", " s = f.read()\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "EmBI1zY8DAvT" }, "source": [ "# Exceptions\n", "\n", "Julia's exceptions behave very much like in Python:" ] }, { "cell_type": "code", "execution_count": 266, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "z-dXwra9cvYD", "outputId": "1ee3f6f4-d756-4783-d400-2dfc233f4bff" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[1, 2, 3, 5]\n" ] } ], "source": [ "a = [1]\n", "try\n", " push!(a, 2)\n", " #throw(\"Oops\") # try uncommenting this line\n", " push!(a, 3)\n", "catch ex\n", " println(ex)\n", " push!(a, 4)\n", "finally\n", " push!(a, 5)\n", "end\n", "println(a)" ] }, { "cell_type": "markdown", "metadata": { "id": "kt2mWkKFfd8I" }, "source": [ "The equivalent Python code is:\n", "\n", "```python\n", "# PYTHON\n", "a = [1]\n", "try:\n", " a.append(2)\n", " #raise Exception(\"Oops\") # try uncommenting this line\n", " a.append(3)\n", "except Exception as ex:\n", " print(ex)\n", " a.append(4)\n", "finally:\n", " a.append(5)\n", "\n", "print(a)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "19zE5t7nhtCJ" }, "source": [ "There is a whole hierarchy of standard exceptions which can be thrown, just like in Python. For example:" ] }, { "cell_type": "code", "execution_count": 267, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "71J-OuM5h9cr", "outputId": "050d1e94-3ec1-4e2c-d5c4-66160cd708ec" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Oops. System error #2 (opening file \"/foo/bar/i_dont_exist.txt\")\n" ] } ], "source": [ "choice = 1 # try changing this value (from 1 to 4)\n", "try\n", " choice == 1 && open(\"/foo/bar/i_dont_exist.txt\")\n", " choice == 2 && sqrt(-1)\n", " choice == 3 && push!(a, \"Oops\")\n", " println(\"Everything worked like a charm\")\n", "catch ex\n", " if ex isa SystemError\n", " println(\"Oops. System error #$(ex.errnum) ($(ex.prefix))\")\n", " elseif ex isa DomainError\n", " println(\"Oh no, I could not compute sqrt(-1)\")\n", " else\n", " println(\"I got an unexpected error: $ex\")\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "0IegY5XckWET" }, "source": [ "Compare this with Python's equivalent code:\n", "\n", "```python\n", "# PYTHON\n", "choice = 3 # try changing this value (from 1 to 4)\n", "try:\n", " if choice == 1:\n", " open(\"/foo/bar/i_dont_exist.txt\")\n", " if choice == 2:\n", " math.sqrt(-1)\n", " if choice == 3:\n", " #a.append(\"Ok\") # this would actually work\n", " raise TypeError(\"Oops\") # so let's fail manually\n", " print(\"Everything worked like a charm\")\n", "except OSError as ex:\n", " print(f\"Oops. OS error (#{ex.errno} ({ex.strerror})\")\n", "except ValueError:\n", " print(\"Oh no, I could not compute sqrt(-1)\")\n", "except Exception as ex:\n", " print(f\"I got an unexpected error: {ex}\")\n", "```\n" ] }, { "cell_type": "markdown", "metadata": { "id": "jD6j5HsCHXzo" }, "source": [ "A few things to note here:\n", "\n", "* Julia only allows a single `catch` block which handles all possible exceptions.\n", "* `obj isa SomeClass` is a shorthand for `isa(obj, SomeClass)` which is equivalent to Python's `isinstance(obj, SomeClass)`.\n", "\n", "|Julia|Python\n", "|-----|------\n", "|`try`
    `...`
`catch ex`
    `if ex isa SomeError`
        `...`
    `else`
        `...`
    `end`
`finally`
    `...`
`end` | `try:`
    `...`
`except SomeException as ex:`
    `...`
`except Exception as ex:`
    `...`
`finally:`
    `...`\n", "|`throw any_value` | `raise SomeException(...)`\n", "| `obj isa SomeType`
or
`isa(obj, SomeType`) | `isinstance(obj, SomeType)`" ] }, { "cell_type": "markdown", "metadata": { "id": "lFGQZmkfQg6K" }, "source": [ "Note that Julia does not support the equivalent of Python's `try / catch / else` construct. You need to write something like this:" ] }, { "cell_type": "code", "execution_count": 268, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "P2qPhf-EQ4WW", "outputId": "5db12b58-19e8-4482-b973-4b5b8a1869b0" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Try something\n", "No error occurred\n", "The end\n", "After the end\n" ] } ], "source": [ "catch_exception = true\n", "try\n", " println(\"Try something\")\n", " #error(\"ERROR: Catch me!\") # try uncommenting this line\n", " catch_exception = false\n", " #error(\"ERROR: Don't catch me!\") # try uncommenting this line\n", " println(\"No error occurred\")\n", "catch ex\n", " if catch_exception\n", " println(\"I caught this exception: $ex\")\n", " else\n", " throw(ex)\n", " end\n", "finally\n", " println(\"The end\")\n", "end\n", "println(\"After the end\")" ] }, { "cell_type": "markdown", "metadata": { "id": "XNyuFEVER7-p" }, "source": [ "The equivalent Python code is shorter, but it's fairly uncommon:\n", "\n", "```python\n", "# PYTHON\n", "try:\n", " print(\"Try something\")\n", " raise Exception(\"Catch me!\") # try uncommenting this line\n", "except Exception as ex:\n", " print(f\"I caught this exception: {ex}\")\n", "else:\n", " raise Exception(\"Don't catch me!\") # try uncommenting this line\n", " print(\"No error occured\")\n", "finally:\n", " print(\"The end\")\n", "\n", "print(\"After the end\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "Ieqz4vrxEAQb" }, "source": [ "# Docstrings\n", "It's good practice to add docstrings to every function you export. The docstring is placed just _before_ the definition of the function:" ] }, { "cell_type": "code", "execution_count": 269, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "OgwehsQjNj8Y", "outputId": "778dfd6c-a940-46f5-8b08-27025ac3762c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "square" ] }, "metadata": {}, "execution_count": 269 } ], "source": [ "\"Compute the square of number x\"\n", "square(x::Number) = x^2" ] }, { "cell_type": "markdown", "metadata": { "id": "B9vmLpLpO6ix" }, "source": [ "You can retrieve a function's docstring using the `@doc` macro:" ] }, { "cell_type": "code", "execution_count": 270, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 46 }, "id": "rYR63pL1NsWU", "outputId": "5a794896-cb4d-4d93-c90a-b01c60d8a436" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ " Compute the square of number x" ], "text/markdown": "Compute the square of number x\n", "text/latex": "Compute the square of number x\n\n" }, "metadata": {}, "execution_count": 270 } ], "source": [ "@doc square" ] }, { "cell_type": "markdown", "metadata": { "id": "HcKzENGnO-8z" }, "source": [ "The docstring is displayed when asking for help:" ] }, { "cell_type": "code", "execution_count": 271, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 82 }, "id": "O9xkw7uvNw9A", "outputId": "a114938d-246a-4df3-8a32-006b151f411e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "search: \u001b[0m\u001b[1ms\u001b[22m\u001b[0m\u001b[1mq\u001b[22m\u001b[0m\u001b[1mu\u001b[22m\u001b[0m\u001b[1ma\u001b[22m\u001b[0m\u001b[1mr\u001b[22m\u001b[0m\u001b[1me\u001b[22m \u001b[0m\u001b[1mS\u001b[22m\u001b[0m\u001b[1mq\u001b[22m\u001b[0m\u001b[1mu\u001b[22m\u001b[0m\u001b[1ma\u001b[22m\u001b[0m\u001b[1mr\u001b[22m\u001b[0m\u001b[1me\u001b[22m My\u001b[0m\u001b[1mS\u001b[22m\u001b[0m\u001b[1mq\u001b[22m\u001b[0m\u001b[1mu\u001b[22m\u001b[0m\u001b[1ma\u001b[22m\u001b[0m\u001b[1mr\u001b[22m\u001b[0m\u001b[1me\u001b[22ms \u001b[0m\u001b[1mS\u001b[22m\u001b[0m\u001b[1mq\u001b[22m\u001b[0m\u001b[1mu\u001b[22m\u001b[0m\u001b[1ma\u001b[22m\u001b[0m\u001b[1mr\u001b[22m\u001b[0m\u001b[1me\u001b[22m_v2 \u001b[0m\u001b[1ms\u001b[22muper \u001b[0m\u001b[1ms\u001b[22murf \u001b[0m\u001b[1ms\u001b[22m\u001b[0m\u001b[1mq\u001b[22mrt quote Base.acquire\n", "\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ " Compute the square of number x" ], "text/markdown": "Compute the square of number x\n", "text/latex": "Compute the square of number x\n\n" }, "metadata": {}, "execution_count": 271 } ], "source": [ "?square" ] }, { "cell_type": "markdown", "metadata": { "id": "wte1Y76jPKpd" }, "source": [ "Docstrings follow the [Markdown format](https://en.wikipedia.org/wiki/Markdown#:~:text=Markdown%20is%20a%20lightweight%20markup,using%20a%20plain%20text%20editor.).\n", "A typical docstring starts with the signature of the function, indented by 4 spaces, so it will get syntax highlighted as Julia code.\n", "It also includes an `Examples` section with Julia REPL outputs:" ] }, { "cell_type": "code", "execution_count": 272, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xucdR5ywNzTf", "outputId": "76111b70-ed8c-4e88-ad34-ce56800fd1d6" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "cube" ] }, "metadata": {}, "execution_count": 272 } ], "source": [ "\"\"\"\n", " cube(x::Number)\n", "\n", "Compute the cube of `x`.\n", "\n", "# Examples\n", "```julia-repl\n", "julia> cube(5)\n", "125\n", "julia> cube(im)\n", "0 - 1im\n", "```\n", "\"\"\"\n", "cube(x) = x^3" ] }, { "cell_type": "markdown", "metadata": { "id": "b2aSKGYkaN7D" }, "source": [ "Instead of using `julia-repl` code blocks for the examples, you can use `jldoctest` to mark these examples as doctests (similar to Python's doctests)." ] }, { "cell_type": "markdown", "metadata": { "id": "gJt_pU7oQFzT" }, "source": [ "The help gets nicely formatted:" ] }, { "cell_type": "code", "execution_count": 273, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 221 }, "id": "FolAC9NdOYlH", "outputId": "f0fa7013-7d2e-4df9-cb5f-4ab6d04aef48" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "search: \u001b[0m\u001b[1mc\u001b[22m\u001b[0m\u001b[1mu\u001b[22m\u001b[0m\u001b[1mb\u001b[22m\u001b[0m\u001b[1me\u001b[22m \u001b[0m\u001b[1mc\u001b[22mlabel Number Tue June \u001b[0m\u001b[1mc\u001b[22mode\n", "\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "\u001b[36m cube(x::Number)\u001b[39m\n", "\n", " Compute the cube of \u001b[36mx\u001b[39m.\n", "\n", "\u001b[1m Examples\u001b[22m\n", "\u001b[1m ≡≡≡≡≡≡≡≡\u001b[22m\n", "\n", "\u001b[36m julia> cube(5)\u001b[39m\n", "\u001b[36m 125\u001b[39m\n", "\u001b[36m julia> cube(im)\u001b[39m\n", "\u001b[36m 0 - 1im\u001b[39m" ], "text/markdown": "```\ncube(x::Number)\n```\n\nCompute the cube of `x`.\n\n# Examples\n\n```julia-repl\njulia> cube(5)\n125\njulia> cube(im)\n0 - 1im\n```\n", "text/latex": "\\begin{verbatim}\ncube(x::Number)\n\\end{verbatim}\nCompute the cube of \\texttt{x}.\n\n\\section{Examples}\n\\begin{verbatim}\njulia> cube(5)\n125\njulia> cube(im)\n0 - 1im\n\\end{verbatim}\n" }, "metadata": {}, "execution_count": 273 } ], "source": [ "?cube" ] }, { "cell_type": "markdown", "metadata": { "id": "bT28xCa9QMcq" }, "source": [ "When there are several methods for a given function, it is common to give general information about the function in the first method (usually the most generic), and only add docstrings to other methods if they add useful information (without repeating the general info).\n", "\n", "Alternatively, you may attach the general information to the function itself:" ] }, { "cell_type": "code", "execution_count": 274, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mi3rw1dYQmkT", "outputId": "826ef50b-203b-40a5-a0c1-a94319898dca" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "foo" ] }, "metadata": {}, "execution_count": 274 } ], "source": [ "\"\"\"\n", " foo(x)\n", "\n", "Compute the foo of the bar\n", "\"\"\"\n", "function foo end # declares the foo function\n", "\n", "# foo(x::Number) behaves normally, no need for a docstring\n", "foo(x::Number) = \"baz\"\n", "\n", "\"\"\"\n", " foo(x::String)\n", "\n", "For strings, compute the qux of the bar instead.\n", "\"\"\"\n", "foo(x::String) = \"qux\"" ] }, { "cell_type": "code", "execution_count": 275, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 156 }, "id": "ffQK0fnPTXsH", "outputId": "23fe07f5-abea-467d-804c-4402e3002021" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "search: \u001b[0m\u001b[1mf\u001b[22m\u001b[0m\u001b[1mo\u001b[22m\u001b[0m\u001b[1mo\u001b[22m \u001b[0m\u001b[1mf\u001b[22m\u001b[0m\u001b[1mo\u001b[22mr \u001b[0m\u001b[1mf\u001b[22ml\u001b[0m\u001b[1mo\u001b[22m\u001b[0m\u001b[1mo\u001b[22mr cool Bool \u001b[0m\u001b[1mf\u001b[22mdi\u001b[0m\u001b[1mo\u001b[22m\n", "\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "\u001b[36m foo(x)\u001b[39m\n", "\n", " Compute the foo of the bar\n", "\n", " ────────────────────────────────────────────────────────────────────────────\n", "\n", "\u001b[36m foo(x::String)\u001b[39m\n", "\n", " For strings, compute the qux of the bar instead." ], "text/markdown": "```\nfoo(x)\n```\n\nCompute the foo of the bar\n\n---\n\n```\nfoo(x::String)\n```\n\nFor strings, compute the qux of the bar instead.\n", "text/latex": "\\begin{verbatim}\nfoo(x)\n\\end{verbatim}\nCompute the foo of the bar\n\n\\rule{\\textwidth}{1pt}\n\\begin{verbatim}\nfoo(x::String)\n\\end{verbatim}\nFor strings, compute the qux of the bar instead.\n\n" }, "metadata": {}, "execution_count": 275 } ], "source": [ "?foo" ] }, { "cell_type": "markdown", "metadata": { "id": "K80HBr5oGzo7" }, "source": [ "# Macros" ] }, { "cell_type": "markdown", "metadata": { "id": "QObp7sGRu6UJ" }, "source": [ "We have seen a few macros already: `@which`, `@assert`, `@time`, `@benchmark`, `@btime` and `@doc`. You guessed it: all macros start with an `@` sign.\n", "\n", "What is a macro? It is a function which can fully inspect the expression that follows it, and apply any transformation to that code at parse time, before compilation.\n", "\n", "This makes it possible for anyone to effectively extend the language in any way they please. Whereas C/C++ macros just do simple text replacement, **Julia macros are powerful meta-programming tools**.\n", "\n", "On the flip side, this also means that **each macro has its own syntax and behavior**.\n", "\n", "**A personal opinion**: in my experience, languages that provide great flexibility typically attract a community of programmers with a tinkering mindset, who will _love_ to experiment with all the fun features the language has to offer. This is great for creativity, but it can also be a nuisance if the community ends up producing too much experimental code, without much care for code reliability, API stability, or even for simplicity. By all means, let's be creative, let's experiment, but _with great power comes great responsibility_: let's also value reliability, stability and simplicity.\n", "\n", "That said, to give you an idea of what macro definitions look like in Julia, here's a simple toy macro that replaces `a + b` expressions with `a - b`, and leaves other expressions alone." ] }, { "cell_type": "code", "execution_count": 276, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "piHUqhKBZGqu", "outputId": "e11db1dc-2e72-4b49-974a-ab48bdcc3b99" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "8" ] }, "metadata": {}, "execution_count": 276 } ], "source": [ "macro addtosub(x)\n", " if x.head == :call && x.args[1] == :+ && length(x.args) == 3\n", " Expr(:call, :-, x.args[2], x.args[3])\n", " else\n", " x\n", " end\n", "end\n", "\n", "@addtosub 10 + 2" ] }, { "cell_type": "markdown", "metadata": { "id": "7rJOpjunbOFk" }, "source": [ "In this macro definition, `:call`, `:+` and `:-` are **symbols**. These are similar to strings, only more efficient and less flexible. They are typically used as identifiers, such as keys in dictionaries." ] }, { "cell_type": "markdown", "metadata": { "id": "MaXNB30jZ0o9" }, "source": [ "If you're curious, the macro works because the parser converts `10 + 2` to `Expr(:call, :+, 10, 2)` and passes this expression to the macro (before compilation). The `if` statement checks that the expression is a function call, where the called function is the `+` function, with two arguments. If so, then the macro returns a new expression, corresponding to a call to the `-` function, with the same arguments. So `a + b` becomes `a - b`." ] }, { "cell_type": "markdown", "metadata": { "id": "tDEx5z5thmJG" }, "source": [ "For more info, check out [this page](https://docs.julialang.org/en/v1/manual/metaprogramming/)." ] }, { "cell_type": "markdown", "metadata": { "id": "MKMAu-SPGaEp" }, "source": [ "## Special Prefixed Strings" ] }, { "cell_type": "markdown", "metadata": { "id": "RiczKNd3Evgz" }, "source": [ "`py\"...\"` strings are defined by the `PyCall` module. Writing `py\"something\"` is equivalent to writing `@py_str \"something\"`. In other words, anyone can write a macro that defines a new kind of prefixed string. For example, if you write the `@ok_str` macro, it will be called when you write `ok\"something\"`.\n", "\n", "Another example is the `Pkg` module which defines the `@pkg_str` macro: this is why you can use `pkg\"...\"` to interact with the `Pkg` module. This is how `pkg\"add PyCall; precompile;\"` worked (at the end of the very first cell). This downloaded, installed and precompiled the `PyCall` module." ] }, { "cell_type": "markdown", "metadata": { "id": "V2DqnxfIS47B" }, "source": [ "# Modules\n", "In Python, a module must be defined in a dedicated file. In Julia, modules are independent from the file system. You can define several modules per file, or define one module across multiple files, it's up to you. Let's create a simple module containing two submodules, each containing a variable and a function:" ] }, { "cell_type": "code", "execution_count": 277, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "u9tQpD0JS_gm", "outputId": "ad0d2a3d-a86f-4ac2-c7fb-98b83362878f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Main.ModA" ] }, "metadata": {}, "execution_count": 277 } ], "source": [ "module ModA\n", " pi = 3.14\n", " square(x) = x^2\n", "\n", " module ModB\n", " e = 2.718\n", " cube(x) = x^3\n", " end\n", "\n", " module ModC\n", " root2 = √2\n", " relu(x) = max(0, x)\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "olgUIIbvYBQq" }, "source": [ "The default module is `Main`, so whatever we define is put in this module (except when defining a package, as we will see). This is why the `ModA`'s full name is `Main.ModA`." ] }, { "cell_type": "markdown", "metadata": { "id": "bnP_rALhVlw3" }, "source": [ "We can now access the contents of these modules by providing the full paths:" ] }, { "cell_type": "code", "execution_count": 278, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IFOTvRKXXVNr", "outputId": "b14c97a8-8fe1-4dea-a3ac-a01b3f437273" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.4142135623730951" ] }, "metadata": {}, "execution_count": 278 } ], "source": [ "Main.ModA.ModC.root2" ] }, { "cell_type": "markdown", "metadata": { "id": "Z-ooQk9zYY1N" }, "source": [ "Since our code runs in the `Main` module, we can leave out the `Main.` part:" ] }, { "cell_type": "code", "execution_count": 279, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "JnnhVg1eYhLz", "outputId": "48594224-406c-4e66-afcb-cce425b30289" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.4142135623730951" ] }, "metadata": {}, "execution_count": 279 } ], "source": [ "ModA.ModC.root2" ] }, { "cell_type": "markdown", "metadata": { "id": "7liRTwI2Xd5e" }, "source": [ "Alternatively, you can use `import`:" ] }, { "cell_type": "code", "execution_count": 280, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DxT44ZvnXoGN", "outputId": "d2c9ff3b-8472-4850-dabd-75c2efe8f272" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.4142135623730951" ] }, "metadata": {}, "execution_count": 280 } ], "source": [ "import Main.ModA.ModC.root2\n", "\n", "root2" ] }, { "cell_type": "markdown", "metadata": { "id": "TpOjKGi8Ymmu" }, "source": [ "Or we can use `import` with a relative path. In this case, we need to prefix `ModA` with a dot `.` to indicate that we want the module `ModA` located in the current module:" ] }, { "cell_type": "code", "execution_count": 281, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9kqmiNNTYpQY", "outputId": "b07548e1-2a5a-40c3-cd48-694e03631fe3" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.4142135623730951" ] }, "metadata": {}, "execution_count": 281 } ], "source": [ "import .ModA.ModC.root2\n", "\n", "root2" ] }, { "cell_type": "markdown", "metadata": { "id": "U1jBsBSyYshg" }, "source": [ "Alternatively, we can `import` the submodule:" ] }, { "cell_type": "code", "execution_count": 282, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5Xui2RQEZROC", "outputId": "ff1c21ac-87d1-4a3d-b90c-f42c57cfa20e" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1.4142135623730951" ] }, "metadata": {}, "execution_count": 282 } ], "source": [ "import .ModA.ModC\n", "\n", "ModC.root2" ] }, { "cell_type": "markdown", "metadata": { "id": "8mz_KKlAZXPH" }, "source": [ "When you want to import more than one name from a module, you can use this syntax:" ] }, { "cell_type": "code", "execution_count": 283, "metadata": { "id": "qtmDevYgZh5I" }, "outputs": [], "source": [ "import .ModA.ModC: root2, relu" ] }, { "cell_type": "markdown", "metadata": { "id": "T1nyVSefZotn" }, "source": [ "This is equivalent to this more verbose syntax:" ] }, { "cell_type": "code", "execution_count": 284, "metadata": { "id": "s-i-7QIGZrQn" }, "outputs": [], "source": [ "import .ModA.ModC.root2, .ModA.ModC.relu" ] }, { "cell_type": "markdown", "metadata": { "id": "rJSA9aljZ3BN" }, "source": [ "Nested modules do not automatically have access to names in enclosing modules. To import names from a parent module, use `..x`. From a grand-parent module, use `...x`, and so on." ] }, { "cell_type": "code", "execution_count": 285, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "NvRtf5SWZ_Wk", "outputId": "aba6b3b4-2d51-4a91-8ef3-82ce432509ef" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "UndefVarError(:d, Main.ModD.ModE)\n", "2\n", "1\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "Main.ModD" ] }, "metadata": {}, "execution_count": 285 } ], "source": [ "module ModD\n", " d = 1\n", " module ModE\n", " try\n", " println(d)\n", " catch ex\n", " println(ex)\n", " end\n", " end\n", " module ModF\n", " f = 2\n", " module ModG\n", " import ..f\n", " import ...d\n", " println(f)\n", " println(d)\n", " end\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "y_YxkO1ebZEb" }, "source": [ "Instead of `import`, you can use `using`. It is analog to Python's `from foo import *`. It only gives access to names which were explicitly exported using `export` (similar to the way `from foo import *` in Python only imports names listed in the module's `__all__` list):" ] }, { "cell_type": "code", "execution_count": 286, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "CNDHFWv2blSk", "outputId": "aafb0962-f4c4-444f-dbf5-4d0fd8ebb6d8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Main.ModH" ] }, "metadata": {}, "execution_count": 286 } ], "source": [ "module ModH\n", " h1 = 1\n", " h2 = 2\n", " export h1\n", "end" ] }, { "cell_type": "code", "execution_count": 287, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "TrlkNAZjcDpv", "outputId": "4413495e-13be-4116-e5e3-27655f26ee9c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "UndefVarError(:h2, Main)" ] }, "metadata": {}, "execution_count": 287 } ], "source": [ "using .ModH\n", "\n", "println(h1)\n", "\n", "try\n", " println(h2)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "XA-8xz9XBkFr" }, "source": [ "Note that `using Foo` not only imports all exported names (like Python's `from foo import *`), it also imports `Foo` itself (similarly, `using Foo.Bar` imports `Bar` itself):\n" ] }, { "cell_type": "code", "execution_count": 288, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "b2j0lGheB4DK", "outputId": "ade72977-e649-441d-8acb-039decf615e2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Main.ModH" ] }, "metadata": {}, "execution_count": 288 } ], "source": [ "ModH" ] }, { "cell_type": "markdown", "metadata": { "id": "ZJr74QBRzuEo" }, "source": [ "Even if a name is not exported, you can always access it using its full path, or using `import`:" ] }, { "cell_type": "code", "execution_count": 289, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yjCkh4voz44p", "outputId": "717a10d8-0e21-4e52-bab2-fb0f0cddc1ae" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 289 } ], "source": [ "ModH.h2" ] }, { "cell_type": "code", "execution_count": 290, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "SAL1D1dRz7oy", "outputId": "5100ba77-9071-42c0-a12b-2161fbb18094" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 290 } ], "source": [ "import .ModH.h2\n", "\n", "h2" ] }, { "cell_type": "markdown", "metadata": { "id": "1UfcZp6fcNdT" }, "source": [ "You can also import individual names like this:" ] }, { "cell_type": "code", "execution_count": 291, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fQPAWJMRzOmj", "outputId": "07a4dc5c-5212-4455-a594-6405f8bc1758" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "2\n" ] } ], "source": [ "module ModG\n", " g1 = 1\n", " g2 = 2\n", " export g2\n", "end\n", "\n", "using .ModG: g1, g2\n", "\n", "println(g1)\n", "println(g2)" ] }, { "cell_type": "markdown", "metadata": { "id": "Y5ToJpN_0RMa" }, "source": [ "Notice that this syntax gives you access to any name you want, whether or not it was exported. In other words, whether a name is exported or not only affects the `using Foo` syntax." ] }, { "cell_type": "markdown", "metadata": { "id": "yzXfmlAg0diH" }, "source": [ "Importantly, when you want to expand a function which is defined in a module, you must import the function using `import`, or you must specify the function's path:" ] }, { "cell_type": "code", "execution_count": 292, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BfOgteoF0m4X", "outputId": "b53437ae-f36f-4d35-a2f1-aaf7114c2c90" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "4\n", "TwoTwo\n", "9\n", "ThreeThreeThree\n" ] }, { "output_type": "stream", "name": "stderr", "text": [ "WARNING: replacing module ModH.\n" ] } ], "source": [ "module ModH\n", " double(x) = x * 2\n", " triple(x) = x * 3\n", "end\n", "\n", "import .ModH: double\n", "double(x::AbstractString) = repeat(x, 2)\n", "\n", "ModH.triple(x::AbstractString) = repeat(x, 3)\n", "\n", "println(double(2))\n", "println(double(\"Two\"))\n", "\n", "println(ModH.triple(3))\n", "println(ModH.triple(\"Three\"))" ] }, { "cell_type": "markdown", "metadata": { "id": "c4c2yTaS7fvj" }, "source": [ "You must never extend a function imported with `using`, unless you provide the function's path:" ] }, { "cell_type": "code", "execution_count": 293, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "18mmhRsP18o_", "outputId": "265ef33a-ff91-4b5c-b54a-d6d0c5ed716e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "16\n", "FourFourFourFour\n" ] } ], "source": [ "module ModI\n", " quadruple(x) = x * 4\n", " export quadruple\n", "end\n", "\n", "using .ModI\n", "ModI.quadruple(x::AbstractString) = repeat(x, 4) # OK\n", "println(quadruple(4))\n", "println(quadruple(\"Four\"))\n", "\n", "#quadruple(x::AbstractString) = repeat(x, 4) # uncomment to see the error" ] }, { "cell_type": "markdown", "metadata": { "id": "0gwWiNqfD-Pp" }, "source": [ "There is no equivalent of Python's `import foo as x` ([yet](https://github.com/JuliaLang/julia/issues/1255)), but you can do something like this:" ] }, { "cell_type": "code", "execution_count": 294, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2eGsfHqkEViW", "outputId": "3bfc921b-0711-4896-bdef-af950a6de5f0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "quadruple (generic function with 2 methods)" ] }, "metadata": {}, "execution_count": 294 } ], "source": [ "import .ModI: quadruple\n", "x = quadruple" ] }, { "cell_type": "markdown", "metadata": { "id": "pB4HxazHyNiF" }, "source": [ "In general, a module named `Foo` will be defined in a file named `Foo.jl` (along with its submodules). However, if the module becomes too big for a single file, you can split it into multiple files and include these files in `Foo.jl` using the `include()` function.\n", "\n", "For example, let's create three files: `Awesome.jl`, `great.jl` and `amazing/Fantastic.jl`, where:\n", "* `Awesome.jl` defines the `Awesome` module and includes the other two files\n", "* `great.jl` just defines a function\n", "* `amazing/Fantastic.jl` defines the `Fantastic` submodule" ] }, { "cell_type": "code", "execution_count": 295, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "A9iOU4XWundK", "outputId": "59f9a4e4-9adf-4e99-c9c6-36fea16935a0" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "38" ] }, "metadata": {}, "execution_count": 295 } ], "source": [ "code_awesome = \"\"\"\n", "module Awesome\n", "include(\"great.jl\")\n", "include(\"amazing/Fantastic.jl\")\n", "end\n", "\"\"\"\n", "\n", "code_great = \"\"\"\n", "great() = \"This is great!\"\n", "\"\"\"\n", "\n", "code_fantastic = \"\"\"\n", "module Fantastic\n", "fantastic = true\n", "end\n", "\"\"\"\n", "\n", "open(f->write(f, code_awesome), \"Awesome.jl\", \"w\")\n", "open(f->write(f, code_great), \"great.jl\", \"w\")\n", "mkdir(\"amazing\")\n", "open(f->write(f, code_fantastic), \"amazing/Fantastic.jl\", \"w\")" ] }, { "cell_type": "markdown", "metadata": { "id": "3OYEv-P-xGHg" }, "source": [ "If we try to execute `import Awesome` now, it won't work since Julia does not search in the current directory by default. Let's change this:" ] }, { "cell_type": "code", "execution_count": 296, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Zz-JHOzXxZj5", "outputId": "0f73af48-861e-4dc6-a7e6-806ee1247ddc" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{String}:\n", " \".\"\n", " \"@\"\n", " \"@v#.#\"\n", " \"@stdlib\"" ] }, "metadata": {}, "execution_count": 296 } ], "source": [ "pushfirst!(LOAD_PATH, \".\")" ] }, { "cell_type": "markdown", "metadata": { "id": "L8H2XLMAww7g" }, "source": [ "Now when we import the `Awesome` module, Julia will look for a file named `Awesome.jl` in the current directory, or for `Awesome/src/Awesome.jl`, or for `Awesome.jl/src/Awesome.jl`. If it does not find any of these, it will look in the other places listed in the `LOAD_PATH` array (we will discuss this in more details in the \"Package Management\" section)." ] }, { "cell_type": "code", "execution_count": 297, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "V4crjErQvuYf", "outputId": "53d5158b-c37f-4b1f-b80d-c26e3d425919" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[36m\u001b[1m[ \u001b[22m\u001b[39m\u001b[36m\u001b[1mInfo: \u001b[22m\u001b[39mPrecompiling Awesome [top-level] \n" ] }, { "output_type": "stream", "name": "stdout", "text": [ "This is great!\n", "Is fantastic? true\n" ] } ], "source": [ "import Awesome\n", "println(Awesome.great())\n", "println(\"Is fantastic? \", Awesome.Fantastic.fantastic)" ] }, { "cell_type": "markdown", "metadata": { "id": "zpLsSNQbttj1" }, "source": [ "Let's restore the original `LOAD_PATH`:" ] }, { "cell_type": "code", "execution_count": 298, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zV4wd4Natxw7", "outputId": "c0e6ddf1-1d22-481e-b0e9-957e76af033d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\".\"" ] }, "metadata": {}, "execution_count": 298 } ], "source": [ "popfirst!(LOAD_PATH)" ] }, { "cell_type": "markdown", "metadata": { "id": "Jz7_FaJcaZn-" }, "source": [ "In short:\n", "\n", "|Julia | Python\n", "|------|-------\n", "|`import Foo` | `import foo`\n", "|`import Foo.Bar` | `from foo import bar`\n", "|`import Foo.Bar: a, b` | `from foo.bar import a, b`\n", "|`import Foo.Bar.a, Foo.Bar.b` | `from foo.bar import a, b`\n", "|`import .Foo` | `import .foo`\n", "|`import ..Foo.Bar` | `from ..foo import bar`\n", "|`import ...Foo.Bar` | `from ...foo import bar`\n", "|`import .Foo: a, b` | `from .foo import a, b`\n", "||\n", "|`using Foo` | `from foo import *; import foo`\n", "|`using Foo.Bar` | `from foo.bar import *; from foo import bar `\n", "|`using Foo.Bar: a, b` | `from foo.bar import a, b`\n", "\n", "|Extending function `Foo.f()` | Result\n", "|-----------------------------|--------\n", "|`import Foo.f # or Foo: f`
`f(x::Int64) = ...` | OK\n", "|`import Foo`
`Foo.f(x::Int64) = ...` | OK\n", "|`using Foo`
`Foo.f(x::Int64) = ...` | OK\n", "|`import Foo.f # or Foo: f`
`Foo.f(x::Int64) = ...` | `ERROR: Foo not defined`\n", "|`using Foo`
`f(x::Int64) = ...` | `ERROR: Foo.f must be explicitly imported`\n", "|`using Foo: f`
`f(x::Int64) = ...` | `ERROR: Foo.f must be explicitly imported`" ] }, { "cell_type": "markdown", "metadata": { "id": "Inpk8qS3HL_u" }, "source": [ "# Scopes\n", "Julia has two types of scopes: global and local.\n", "\n", "Every module has its own global scope, independent from all other global scopes. There is no overarching global scope.\n", "\n", "Modules, macros and types (including structs) can only be defined in a global scope.\n", "\n", "Most code blocks, including `function`, `struct`, `for`, `while`, etc., have their own local scope. For example:" ] }, { "cell_type": "code", "execution_count": 299, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "q3Lm3T1DG6St", "outputId": "9b30f7c4-0448-41ea-f31e-aeade809bb07" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "2\n", "3\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "UndefVarError(:q, Main)" ] }, "metadata": {}, "execution_count": 299 } ], "source": [ "for q in 1:3\n", " println(q)\n", "end\n", "\n", "try\n", " println(q) # q is not available here\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Wh6xa4fyJLYY" }, "source": [ "A local scope inherits from its parent scope:" ] }, { "cell_type": "code", "execution_count": 300, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IeLfjf4ZJQ41", "outputId": "52deaf4e-f8f2-4cdc-defd-834650418925" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "50\n", "100\n", "150\n" ] } ], "source": [ "z = 5\n", "for i in 1:3\n", " w = 10\n", " println(i * w * z) # i and w are local, z is from the parent scope\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "OQLfwoD3NoC_" }, "source": [ "An inner scope can assign to a variable in the parent scope, if the parent scope is not global:" ] }, { "cell_type": "code", "execution_count": 301, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fwN1C2BhNxyZ", "outputId": "63d41575-3b38-4010-9f62-f5cc7b4720e2" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "5\n", "5\n", "5\n" ] } ], "source": [ "for i in 1:3\n", " s = 0\n", " for j in 1:5\n", " s = j # variable s is from the parent scope\n", " end\n", " println(s)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "0m3aoj8UKxS4" }, "source": [ "You can force a variable to be local by using the `local` keyword:" ] }, { "cell_type": "code", "execution_count": 302, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hlireAo6K3eS", "outputId": "811f4551-1830-4bfb-9038-9a9b8bcf1320" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "0\n", "0\n", "0\n" ] } ], "source": [ "for i in 1:3\n", " s = 0\n", " for j in 1:5\n", " local s = j # variable s is local now\n", " end\n", " println(s)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "qwo2DWNIN8oR" }, "source": [ "To assign to a global variable, you must declare the variable as `global` in the local scope:" ] }, { "cell_type": "code", "execution_count": 303, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "rPnibZjLOFHv", "outputId": "fbb84325-fc9a-4148-a5d6-8b523f45bc60" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3" ] }, "metadata": {}, "execution_count": 303 } ], "source": [ "for i in 1:3\n", " global p\n", " p = i\n", "end\n", "p" ] }, { "cell_type": "markdown", "metadata": { "id": "tL5AgzVjOKuo" }, "source": [ "There is one exception to this rule: when executing code directly in the REPL (since Julia 1.5) or in IJulia, you do not need to declare a variable as `global` if the global variable already exists:" ] }, { "cell_type": "code", "execution_count": 304, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PKZPY8XOOlT9", "outputId": "9565ca27-51de-4465-a452-4be661f75c81" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3" ] }, "metadata": {}, "execution_count": 304 } ], "source": [ "s = 0\n", "for i in 1:3\n", " s = i # implicitly global s: only in REPL Julia 1.5+ or IJulia\n", "end\n", "s" ] }, { "cell_type": "markdown", "metadata": { "id": "Ne7zn3qgPo6M" }, "source": [ "In functions, assigning to a variable which is not explicitly declared as global always makes it local (even in the REPL and IJulia):" ] }, { "cell_type": "code", "execution_count": 305, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "RJgKv_6oPhH2", "outputId": "493c4eb3-a995-4342-d2fe-7cc438812080" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "20\n", "1\n" ] } ], "source": [ "s, t = 1, 2 # globals\n", "\n", "function foo()\n", " s = 10 * t # s is local, t is global\n", "end\n", "\n", "println(foo())\n", "println(s)" ] }, { "cell_type": "markdown", "metadata": { "id": "DhnbEml6SiTG" }, "source": [ "Just like in Python, functions can capture variables from the enclosing scope (not from the scope the function is called from):" ] }, { "cell_type": "code", "execution_count": 306, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "B1lAq94XS6yX", "outputId": "ac7ed330-020e-499c-c8fa-95899acddee1" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n" ] } ], "source": [ "t = 1\n", "\n", "foo() = t # foo() captures t from the global scope\n", "\n", "function bar()\n", " t = 5 # this is a new local variable\n", " println(foo()) # foo() still uses t from the global scope\n", "end\n", "\n", "bar()" ] }, { "cell_type": "code", "execution_count": 307, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "a460UCKFTXt7", "outputId": "736002bd-3870-45aa-d5d0-74b69198c51c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "5\n" ] } ], "source": [ "function quz()\n", " global t\n", " t = 5 # we change the global t\n", " println(foo()) # and this affects foo()\n", "end\n", "\n", "quz()" ] }, { "cell_type": "markdown", "metadata": { "id": "MKsqCB48UxiU" }, "source": [ "Closures work much like in Python:" ] }, { "cell_type": "code", "execution_count": 308, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "CvyNc-ksT22C", "outputId": "38b014c3-185c-4a18-966a-34522bcd6342" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "10" ] }, "metadata": {}, "execution_count": 308 } ], "source": [ "function create_multiplier(n)\n", " function mul(x)\n", " x * n # variable n is captured from the parent scope\n", " end\n", "end\n", "\n", "mul2 = create_multiplier(2)\n", "mul2(5)" ] }, { "cell_type": "markdown", "metadata": { "id": "5qAMInDsUtJN" }, "source": [ "An inner function can modify variables from its parent scope:" ] }, { "cell_type": "code", "execution_count": 309, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "s0JpWoqcU9-P", "outputId": "25f80c17-9440-4b3a-c513-dd4137cb0f45" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "2\n" ] } ], "source": [ "function create_counter()\n", " c = 0\n", " inc() = c += 1 # this inner function modifies the c from the outer function\n", "end\n", "\n", "cnt = create_counter()\n", "println(cnt())\n", "println(cnt())" ] }, { "cell_type": "markdown", "metadata": { "id": "4ai7b0ZAlrjT" }, "source": [ "Consider the following code, and see if you can figure out why it prints the same result multiple times:" ] }, { "cell_type": "code", "execution_count": 310, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eEe2LtvSl0Ea", "outputId": "8e005342-d256-4343-8c45-70ed40dc351f" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "36\n", "36\n", "36\n", "36\n", "36\n" ] } ], "source": [ "funcs = []\n", "i = 1\n", "while i ≤ 5\n", " push!(funcs, ()->i^2)\n", " global i += 1\n", "end\n", "for fn in funcs\n", " println(fn())\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "ryvtm8pImQlB" }, "source": [ "The answer is that there is a single variable `i`, which is captured by all 5 closures. By the time these closures are executed, the value of `i` is 6, so the square is 36, for every closure." ] }, { "cell_type": "markdown", "metadata": { "id": "PWkRl2Yfm4A8" }, "source": [ "If we use a `for` loop, we don't have this problem, since a new local variable is created at every iteration:" ] }, { "cell_type": "code", "execution_count": 311, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zRrKzMHjnBD_", "outputId": "34a92961-2e5a-4a35-cb5e-8e4c4ee7dbe0" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "4\n", "9\n", "16\n", "25\n" ] } ], "source": [ "funcs = []\n", "for i in 1:5\n", " push!(funcs, ()->i^2)\n", "end\n", "for fn in funcs\n", " println(fn())\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "URV8meLypzMG" }, "source": [ "Any local variable created within a `for` loop, a `while` loop or a comprehension also get a new copy at each iteration. So we could code the above example like this:" ] }, { "cell_type": "code", "execution_count": 312, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xjXXASVEqBim", "outputId": "cc47196a-4a51-4123-81ff-cba0f00d1bc9" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "4\n", "9\n", "16\n", "25\n" ] } ], "source": [ "funcs = []\n", "i = 1\n", "while i ≤ 5 # since we are in a while loop...\n", " global i\n", " local j = i # ...and j is created here, it's a new `j` at each iteration\n", " push!(funcs, ()->j^2)\n", " i += 1\n", "end\n", "for fn in funcs\n", " println(fn())\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Vazl0dRDnLpk" }, "source": [ "Another way to get the same result is to use a `let` block, which also creates a new local variable every time it is executed:" ] }, { "cell_type": "code", "execution_count": 313, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "X-HuxLIdnSgB", "outputId": "5be09d02-bf43-4193-a092-54127e127415" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "0\n", "1\n", "4\n", "9\n", "16\n" ] } ], "source": [ "funcs = []\n", "i = 0\n", "while i < 5\n", " let i=i\n", " push!(funcs, ()->i^2)\n", " end\n", " global i += 1\n", "end\n", "for fn in funcs\n", " println(fn())\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "fXEf0zs0no--" }, "source": [ "This `let i=i` block defines a new local variable `i` at every iteration, and initializes it with the value of `i` from the parent scope. Therefore each closure captures a different local variable `i`." ] }, { "cell_type": "markdown", "metadata": { "id": "TKWWfJacoDU0" }, "source": [ "Variables in a `let` block are initialized from left to right, so they can access variables on their left:" ] }, { "cell_type": "code", "execution_count": 314, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "HPXugdyUoH6R", "outputId": "20ad21cd-3aa0-4dfa-c0af-e59fb58b2403" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=2, b=2\n" ] } ], "source": [ "a = 1\n", "let a=a+1, b=a\n", " println(\"a=$a, b=$b\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "S9-frzGloRln" }, "source": [ "In this example, the local variable `a` is initialized with the value of `a + 1`, where `a` comes from the parent scope (i.e., it's the global `a` in this case). However, `b` is initialized with the value of the local `a`, since it now hides the variable `a` from the parent scope." ] }, { "cell_type": "markdown", "metadata": { "id": "SEI5E2xbgYHl" }, "source": [ "Default values in function arguments also have this left-to-right scoping logic:" ] }, { "cell_type": "code", "execution_count": 315, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "S-E8aGtPgBCx", "outputId": "b1e7c00d-fd6b-41d9-f732-23f67164d79d" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "a=2, b=2\n", "a=5, b=5\n" ] } ], "source": [ "a = 1\n", "foobar(a=a+1, b=a) = println(\"a=$a, b=$b\")\n", "foobar()\n", "foobar(5)" ] }, { "cell_type": "markdown", "metadata": { "id": "PzqwY5FngkML" }, "source": [ "In this example, the first argument's default value is `a + 1`, where `a` comes from the parent scope (i.e., the global `a` in this case). However, the second argument's default value is `a`, where `a` in this case is the value of the first argument (not the parent scope's `a`)." ] }, { "cell_type": "markdown", "metadata": { "id": "Au3RUzMsHWw8" }, "source": [ "Note that `if` blocks and `begin` blocks do not have their own local scope, they just use the parent scope:" ] }, { "cell_type": "code", "execution_count": 316, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iARzIFgtpOfB", "outputId": "c5dde211-bdd2-4861-980e-b7445b15db90" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 316 } ], "source": [ "a = 1\n", "if true\n", " a = 2 # same `a` as above\n", "end\n", "a" ] }, { "cell_type": "code", "execution_count": 317, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "kO5v8sfCHogx", "outputId": "2bdd0e11-89e5-428b-cc3b-9683b957f3d2" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 317 } ], "source": [ "a = 1\n", "begin\n", " a = 2 # same `a` as above\n", "end\n", "a" ] }, { "cell_type": "markdown", "metadata": { "id": "2ME2_ryels7L" }, "source": [ "# Package Management\n" ] }, { "cell_type": "markdown", "metadata": { "id": "hsT_nU1gB6-r" }, "source": [ "## Basic Workflow\n", "The simplest way to write a Julia program is to create a `.jl` file somewhere and run it using `julia`. You would usually do this with your favorite editor, but in this notebook we must do this programmatically. For example:" ] }, { "cell_type": "code", "execution_count": 318, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sRL8oZDaC0PX", "outputId": "43175b20-af9a-4da1-8e83-f15d7c096710" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "23" ] }, "metadata": {}, "execution_count": 318 } ], "source": [ "code = \"\"\"\n", "println(\"Hello world\")\n", "\"\"\"\n", "\n", "open(f->write(f, code), \"my_program1.jl\", \"w\")" ] }, { "cell_type": "markdown", "metadata": { "id": "I3LsIKh47R99" }, "source": [ "Then let's run the program using a shell command:" ] }, { "cell_type": "code", "execution_count": 319, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UvfGZxERDv6c", "outputId": "b371150b-ffba-492e-a0a6-15466d24937e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hello world\n" ] } ], "source": [ ";julia my_program1.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "KmDPtknrD0Al" }, "source": [ "If you need to use a package which is not part of the standard library, such as `PyCall`, you first need to install it using Julia's package manager `Pkg`:" ] }, { "cell_type": "code", "execution_count": 320, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "doKyY1qWEMZV", "outputId": "b632bf4a-086d-4306-e5af-f082aeb10288" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Manifest.toml`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "using Pkg\n", "Pkg.add(\"PyCall\")" ] }, { "cell_type": "markdown", "metadata": { "id": "iQj8OgUKERBz" }, "source": [ "Alternatively, in interactive mode, you can enter the `Pkg` mode by typing `]`, then type a command:" ] }, { "cell_type": "code", "execution_count": 321, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KGyakk8GEf84", "outputId": "b43646ce-6123-460e-e9d4-9c1e2525bc82" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Manifest.toml`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "]add PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "AsGGVOPzEkj9" }, "source": [ "You can also precompile the new package to avoid the compilation delay when the package is first used:" ] }, { "cell_type": "code", "execution_count": 322, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GqYovSwVEihb", "outputId": "17f4a26c-f953-4003-9d9f-2651e00cd76f" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Manifest.toml`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "]add PyCall; precompile;" ] }, { "cell_type": "markdown", "metadata": { "id": "JMwXNbzGExCn" }, "source": [ "One last alternative is to use `pkg\"...\"` strings to run commands in your programs:" ] }, { "cell_type": "code", "execution_count": 323, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tx5hdeO9E71F", "outputId": "209aaf58-5050-40d5-c1c6-509a5faa493d" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.11/Manifest.toml`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "pkg\"add PyCall; precompile;\"" ] }, { "cell_type": "markdown", "metadata": { "id": "fEIWFBgnFC6c" }, "source": [ "Now you can import `PyCall` in any of your Julia programs:" ] }, { "cell_type": "code", "execution_count": 324, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9kTp497AFJuT", "outputId": "62370a29-b9a6-40b0-d3f2-70e69e3584df" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "41" ] }, "metadata": {}, "execution_count": 324 } ], "source": [ "code = \"\"\"\n", "using PyCall\n", "py\"print('1 + 2 =', 1 + 2)\"\n", "\"\"\"\n", "\n", "open(f->write(f, code), \"my_program2.jl\", \"w\")" ] }, { "cell_type": "code", "execution_count": 325, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "unfHQR31FVf1", "outputId": "041b731f-6ba7-48c0-b655-19944b1bf8e7" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1 + 2 = 3\n" ] } ], "source": [ ";julia my_program2.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "7Cd3wPG9PH-p" }, "source": [ "You can also add packages by providing their URL (typically on github). This is useful when you want to use a package which is not in the [official Julia Package registry](https://github.com/JuliaRegistries/General), or when you want the very latest version of a package:" ] }, { "cell_type": "code", "execution_count": 326, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cW6k8F91PT23", "outputId": "5f3e3e1b-fa15-4b94-aa62-fe76ad947f22" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Cloning\u001b[22m\u001b[39m git-repo `https://github.com/JuliaLang/Example.jl`\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaLang/Example.jl`\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 732.5 ms\u001b[32m ✓ \u001b[39mExample\n", " 1 dependency successfully precompiled in 2 seconds. 491 already precompiled.\n" ] } ], "source": [ "]add https://github.com/JuliaLang/Example.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "HtRAtwWkD3Ao" }, "source": [ "You can install a specific package version like this:" ] }, { "cell_type": "code", "execution_count": 327, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "CceFEwcTCAWG", "outputId": "0c7e5565-6bd5-45bb-a3ff-e0e2ef6a417d" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m PyCall ─ v1.96.2\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", "\u001b[32m⌃\u001b[39m \u001b[90m[438e738f] \u001b[39m\u001b[95m↓ PyCall v1.96.4 ⇒ v1.96.2\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", "\u001b[32m⌃\u001b[39m \u001b[90m[438e738f] \u001b[39m\u001b[95m↓ PyCall v1.96.4 ⇒ v1.96.2\u001b[39m\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m Packages marked with \u001b[32m⌃\u001b[39m have new versions available and may be upgradable.\n", "\u001b[32m\u001b[1m Building\u001b[22m\u001b[39m PyCall → `~/.julia/scratchspaces/44cfe95a-1eb2-52ea-b672-e2afdf69b78f/1cb97fa63a3629c6d892af4f76fcc4ad8191837c/build.log`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 17943.0 ms\u001b[33m ✓ \u001b[39mPyCall\n", " 1 dependency successfully precompiled in 19 seconds. 491 already precompiled.\n", " \u001b[33m1\u001b[39m dependency precompiled but a different version is currently loaded. Restart julia to access the new version. Otherwise, loading dependents of this package may trigger further precompilation to work with the unexpected version.\n" ] } ], "source": [ "]add PyCall@1.96.2" ] }, { "cell_type": "markdown", "metadata": { "id": "AlN_f9jpGPO1" }, "source": [ "If you only specify version `1` or version `1.96`, Julia will get the latest version with that prefix. For example, `]add PyCall@1.96` would install the latest version `1.96.x`." ] }, { "cell_type": "markdown", "metadata": { "id": "TDixUMn7DDri" }, "source": [ "You can also update a package to its latest version:" ] }, { "cell_type": "code", "execution_count": 328, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "0EQofpkzD4xq", "outputId": "d40f13cd-f5b4-41db-ec74-ff2d492f38aa" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General.toml`\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m↑ PyCall v1.96.2 ⇒ v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m↑ PyCall v1.96.2 ⇒ v1.96.4\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 17768.6 ms\u001b[33m ✓ \u001b[39mPyCall\n", " 1 dependency successfully precompiled in 19 seconds. 491 already precompiled.\n", " \u001b[33m1\u001b[39m dependency precompiled but a different version is currently loaded. Restart julia to access the new version. Otherwise, loading dependents of this package may trigger further precompilation to work with the unexpected version.\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m We haven't cleaned this depot up for a bit, running Pkg.gc()...\n", "\u001b[32m\u001b[1m Active\u001b[22m\u001b[39m manifest files: 1 found\n", "\u001b[32m\u001b[1m Active\u001b[22m\u001b[39m artifact files: 106 found\n", "\u001b[32m\u001b[1m Active\u001b[22m\u001b[39m scratchspaces: 5 found\n", "\u001b[32m\u001b[1m Deleted\u001b[22m\u001b[39m no artifacts, repos, packages or scratchspaces\n" ] } ], "source": [ "]update PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "lgeDVp26DI7_" }, "source": [ "You can update all packages to their latest versions (uncomment if you want to run this, but know that it may take a long time, depending on the number of libraries installed):" ] }, { "cell_type": "code", "execution_count": 329, "metadata": { "id": "S3Mi-zgLDOVw" }, "outputs": [], "source": [ "# ]update" ] }, { "cell_type": "markdown", "metadata": { "id": "1C6nNERsDa_M" }, "source": [ "If you don't want a particular package to be updated the next time you call `]update`, you can pin it:" ] }, { "cell_type": "code", "execution_count": 330, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Nht6o2uCDiiL", "outputId": "5fc0ccdd-4295-4aa3-bb24-30350e5ae5d0" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m~ PyCall v1.96.4 ⇒ v1.96.4 ⚲\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m~ PyCall v1.96.4 ⇒ v1.96.4 ⚲\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "]pin PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "Qp9v2d7tDxjH" }, "source": [ "To unpin the package:" ] }, { "cell_type": "code", "execution_count": 331, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3i9JoQimDl7I", "outputId": "dd1df45e-486e-4833-ce98-603ca1b41848" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m~ PyCall v1.96.4 ⚲ ⇒ v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[93m~ PyCall v1.96.4 ⚲ ⇒ v1.96.4\u001b[39m\n", "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n" ] } ], "source": [ "]free PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "Asr1snFp609f" }, "source": [ "You can also run the tests defined in a package:" ] }, { "cell_type": "code", "execution_count": 332, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XlmKnEvf67zi", "outputId": "89cce3f2-5953-4131-9d8f-cd2998a4a133" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "┌ Warning: CUDA version 12.4.0 in /usr/lib64-nvidia/libcuda.so.1 not supported with this version of Reactant (min supported: 12.6)\n", "└ @ Main ~/.julia/packages/Reactant_jll/M7uc2/.pkg/platform_augmentation.jl:103\n", "\u001b[32m\u001b[1m Testing\u001b[22m\u001b[39m Example\n", "\u001b[32m\u001b[1m Status\u001b[22m\u001b[39m `/tmp/jl_FKYY3l/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39mExample v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\n", " \u001b[90m[8dfed614] \u001b[39mTest v1.11.0\n", "\u001b[32m\u001b[1m Status\u001b[22m\u001b[39m `/tmp/jl_FKYY3l/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39mExample v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\n", " \u001b[90m[2a0f44e3] \u001b[39mBase64 v1.11.0\n", " \u001b[90m[b77e0a4c] \u001b[39mInteractiveUtils v1.11.0\n", " \u001b[90m[56ddb016] \u001b[39mLogging v1.11.0\n", " \u001b[90m[d6f4376e] \u001b[39mMarkdown v1.11.0\n", " \u001b[90m[9a3f8284] \u001b[39mRandom v1.11.0\n", " \u001b[90m[ea8e919c] \u001b[39mSHA v0.7.0\n", " \u001b[90m[9e88b42a] \u001b[39mSerialization v1.11.0\n", " \u001b[90m[8dfed614] \u001b[39mTest v1.11.0\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project for configuration --code-coverage=none --color=yes --check-bounds=yes --warn-overwrite=yes --depwarn=yes --inline=yes --startup-file=no --track-allocation=none...\n", " 411.7 ms\u001b[32m ✓ \u001b[39mExample\n", " 1 dependency successfully precompiled in 1 seconds. 6 already precompiled.\n", "\u001b[32m\u001b[1m Testing\u001b[22m\u001b[39m Running tests...\n", "\u001b[32m\u001b[1m Testing\u001b[22m\u001b[39m Example tests passed \n" ] } ], "source": [ "]test Example" ] }, { "cell_type": "markdown", "metadata": { "id": "lHAXzij6ZPPs" }, "source": [ "Of course, you can remove a package:" ] }, { "cell_type": "code", "execution_count": 333, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2fuEjhjzZROg", "outputId": "ec72b01f-0d87-49fe-b722-e0ceacb08bdd" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[91m- Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[91m- Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n" ] } ], "source": [ "]rm Example" ] }, { "cell_type": "markdown", "metadata": { "id": "eeTA6D33CHTv" }, "source": [ "Lastly, you can check which packages are installed using `]status` (or `]st` for short):" ] }, { "cell_type": "code", "execution_count": 334, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "lSqzm_yECL6Y", "outputId": "581ba9ab-4513-4c5d-d15e-89e171ea556c" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\u001b[32m\u001b[1mStatus\u001b[22m\u001b[39m `~/.julia/environments/v1.11/Project.toml`\n", " \u001b[90m[79e6a3ab] \u001b[39mAdapt v4.4.0\n", " \u001b[90m[6e4b80f9] \u001b[39mBenchmarkTools v1.6.2\n", " \u001b[90m[336ed68f] \u001b[39mCSV v0.10.15\n", " \u001b[90m[052768ef] \u001b[39mCUDA v5.9.1\n", "\u001b[32m⌃\u001b[39m \u001b[90m[a93c6f00] \u001b[39mDataFrames v1.8.0\n", " \u001b[90m[7073ff75] \u001b[39mIJulia v1.31.0\n", " \u001b[90m[b2108857] \u001b[39mLux v1.23.0\n", " \u001b[90m[ee78f7c6] \u001b[39mMakie v0.24.6\n", " \u001b[90m[91a5bcdd] \u001b[39mPlots v1.41.1\n", " \u001b[90m[438e738f] \u001b[39mPyCall v1.96.4\n", " \u001b[90m[d330b81b] \u001b[39mPyPlot v2.11.6\n", " \u001b[90m[3c362404] \u001b[39mReactant v0.2.171\n", "\u001b[32m⌃\u001b[39m \u001b[90m[76a88914] \u001b[39mCUDA_Runtime_jll v0.19.1+0\n", "\u001b[36m\u001b[1mInfo\u001b[22m\u001b[39m Packages marked with \u001b[32m⌃\u001b[39m have new versions available and may be upgradable.\n" ] } ], "source": [ "]st" ] }, { "cell_type": "markdown", "metadata": { "id": "VVwtyjNJlL5j" }, "source": [ "For more `Pkg` commands, type `]help`." ] }, { "cell_type": "markdown", "metadata": { "id": "rQY6BRiBvdbU" }, "source": [ "|Julia (in interactive mode) | Python (in a terminal)\n", "|-----|------\n", "|`]status` | `pip freeze`
or
`conda list`\n", "|`]add Foo` | `pip install foo`
or
`conda install foo`\n", "|`]add Foo@1.2` | `pip install foo==1.2`
or
`conda install foo=1.2`\n", "|`]update Foo` | `pip install --upgrade foo`
or
`conda update foo`\n", "|`]pin Foo` | `foo==` in `requirements.txt`
or
`foo=` in `environment.yml`\n", "|`]free Foo` | `foo` in `requirements.txt`
or
`foo` in `environment.yml`\n", "|`]test Foo` | `python -m unittest foo`\n", "|`]rm Foo` | `pip uninstall foo`
or
`conda remove foo`\n", "|`]help` | `pip --help`\n" ] }, { "cell_type": "markdown", "metadata": { "id": "o6KvuaMcFx2N" }, "source": [ "This workflow is fairly simple, but it means that all of your programs will be using the same version of each package. This is analog to installing packages using `pip install` without using virtual environments." ] }, { "cell_type": "markdown", "metadata": { "id": "CaJvX0RcqukU" }, "source": [ "\n", "## Projects\n", "\n", "If you want to have multiple projects, each with different libraries and library versions, you should define **projects**. These are analog to Python virtual environments.\n", "\n", "A project is just a directory containing a `Project.toml` file and a `Manifest.toml` file:\n", "\n", "```\n", "my_project/\n", " Project.toml\n", " Manifest.toml\n", "```\n", "\n", "* `Project.toml` is similar to a `requirements.txt` file (for pip) or `environment.yml` (for conda): it lists the dependencies of the project, and compatibility constraints (e.g., `SomeDependency = 2.5`).\n", "* `Manifest.toml` is an automatically generated file which lists the exact versions and unique IDs (UUIDs) of all the packages that Julia found, based on `Project.toml`. It includes all the implicit dependencies of the project's packages. This is useful to reproduce an environment precisely. Analog to the output of `pip --freeze`.\n", "\n", "By default, the active project is located in `~/.julia/environments/v#.#` (where `#.#` is the Julia version you are using, such as 1.4). You can set a different project when starting Julia:\n", "\n", "```bash\n", "# BASH\n", "julia --project=/path/to/my_project\n", "```\n", "\n", "Or you can set the `JULIA_PROJECT` environment variable:\n", "\n", "```bash\n", "# BASH\n", "export JULIA_PROJECT=/path/to/my_project\n", "julia\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "84iGO38zM3Cz" }, "source": [ "Or you can just activate a project directly in Julia (this is analog to running `source my_project/env/bin/activate` when using virtualenv):" ] }, { "cell_type": "code", "execution_count": 335, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tuXofzisM-bW", "outputId": "7aa3f3eb-4adf-4874-80e2-0a40de15d231" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Activating\u001b[22m\u001b[39m new project at `/content/my_project`\n" ] } ], "source": [ "Pkg.activate(\"my_project\")" ] }, { "cell_type": "markdown", "metadata": { "id": "wOPtEC3UNTsh" }, "source": [ "The `my_project` directory does not exist yet, but it gets created automatically, along with the `Project.toml` and `Manifest.toml` files, when you first add a package:" ] }, { "cell_type": "code", "execution_count": 336, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yLuWH7U8g84W", "outputId": "4998ba54-97da-46cf-abc3-da5a9ac63c84" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Project.toml`\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Manifest.toml`\n", " \u001b[90m[8f4d0f93] \u001b[39m\u001b[92m+ Conda v1.10.2\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[682c06a0] \u001b[39m\u001b[92m+ JSON v0.21.4\u001b[39m\n", " \u001b[90m[1914dd2f] \u001b[39m\u001b[92m+ MacroTools v0.5.16\u001b[39m\n", " \u001b[90m[69de0a69] \u001b[39m\u001b[92m+ Parsers v2.8.3\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[aea7be01] \u001b[39m\u001b[92m+ PrecompileTools v1.2.1\u001b[39m\n", " \u001b[90m[21216c6a] \u001b[39m\u001b[92m+ Preferences v1.5.0\u001b[39m\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", " \u001b[90m[81def892] \u001b[39m\u001b[92m+ VersionParsing v1.3.0\u001b[39m\n", " \u001b[90m[0dad84c5] \u001b[39m\u001b[92m+ ArgTools v1.1.2\u001b[39m\n", " \u001b[90m[56f22d72] \u001b[39m\u001b[92m+ Artifacts v1.11.0\u001b[39m\n", " \u001b[90m[ade2ca70] \u001b[39m\u001b[92m+ Dates v1.11.0\u001b[39m\n", " \u001b[90m[f43a241f] \u001b[39m\u001b[92m+ Downloads v1.6.0\u001b[39m\n", " \u001b[90m[7b1f6079] \u001b[39m\u001b[92m+ FileWatching v1.11.0\u001b[39m\n", " \u001b[90m[b27032c2] \u001b[39m\u001b[92m+ LibCURL v0.6.4\u001b[39m\n", " \u001b[90m[8f399da3] \u001b[39m\u001b[92m+ Libdl v1.11.0\u001b[39m\n", " \u001b[90m[37e2e46d] \u001b[39m\u001b[92m+ LinearAlgebra v1.11.0\u001b[39m\n", " \u001b[90m[a63ad114] \u001b[39m\u001b[92m+ Mmap v1.11.0\u001b[39m\n", " \u001b[90m[ca575930] \u001b[39m\u001b[92m+ NetworkOptions v1.2.0\u001b[39m\n", " \u001b[90m[de0858da] \u001b[39m\u001b[92m+ Printf v1.11.0\u001b[39m\n", " \u001b[90m[9a3f8284] \u001b[39m\u001b[92m+ Random v1.11.0\u001b[39m\n", " \u001b[90m[ea8e919c] \u001b[39m\u001b[92m+ SHA v0.7.0\u001b[39m\n", " \u001b[90m[9e88b42a] \u001b[39m\u001b[92m+ Serialization v1.11.0\u001b[39m\n", " \u001b[90m[fa267f1f] \u001b[39m\u001b[92m+ TOML v1.0.3\u001b[39m\n", " \u001b[90m[cf7118a7] \u001b[39m\u001b[92m+ UUIDs v1.11.0\u001b[39m\n", " \u001b[90m[4ec0a83e] \u001b[39m\u001b[92m+ Unicode v1.11.0\u001b[39m\n", " \u001b[90m[e66e0078] \u001b[39m\u001b[92m+ CompilerSupportLibraries_jll v1.1.1+0\u001b[39m\n", " \u001b[90m[deac9b47] \u001b[39m\u001b[92m+ LibCURL_jll v8.6.0+0\u001b[39m\n", " \u001b[90m[29816b5a] \u001b[39m\u001b[92m+ LibSSH2_jll v1.11.0+1\u001b[39m\n", " \u001b[90m[c8ffd9c3] \u001b[39m\u001b[92m+ MbedTLS_jll v2.28.6+0\u001b[39m\n", " \u001b[90m[14a3606d] \u001b[39m\u001b[92m+ MozillaCACerts_jll v2023.12.12\u001b[39m\n", " \u001b[90m[4536629a] \u001b[39m\u001b[92m+ OpenBLAS_jll v0.3.27+1\u001b[39m\n", " \u001b[90m[83775a58] \u001b[39m\u001b[92m+ Zlib_jll v1.2.13+1\u001b[39m\n", " \u001b[90m[8e850b90] \u001b[39m\u001b[92m+ libblastrampoline_jll v5.11.0+0\u001b[39m\n", " \u001b[90m[8e850ede] \u001b[39m\u001b[92m+ nghttp2_jll v1.59.0+0\u001b[39m\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m Packages marked with \u001b[33m⌅\u001b[39m have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`\n" ] } ], "source": [ "]add PyCall" ] }, { "cell_type": "markdown", "metadata": { "id": "KS6OJjVpTcKX" }, "source": [ "You can also add a package via its URL:" ] }, { "cell_type": "code", "execution_count": 337, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ydZSoyULOlwV", "outputId": "efb120a3-19a4-4a91-f994-e2976c50d17b" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaLang/Example.jl`\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master`\u001b[39m\n" ] } ], "source": [ "]add https://github.com/JuliaLang/Example.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "JKr9G4fDGGLv" }, "source": [ "Let's also add a package with a specific version:" ] }, { "cell_type": "code", "execution_count": 338, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "xM8l4uR2GJsM", "outputId": "1e099d05-757f-40aa-92c2-c9b6b4e01637" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Example ─ v0.3.3\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Project.toml`\n", "\u001b[32m⌃\u001b[39m \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master` ⇒ v0.3.3\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/my_project/Manifest.toml`\n", "\u001b[32m⌃\u001b[39m \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 `https://github.com/JuliaLang/Example.jl#master` ⇒ v0.3.3\u001b[39m\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m Packages marked with \u001b[32m⌃\u001b[39m have new versions available and may be upgradable.\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 325.6 ms\u001b[32m ✓ \u001b[39mExample\n", " 1 dependency successfully precompiled in 0 seconds. 26 already precompiled.\n" ] } ], "source": [ "]add Example@0.3" ] }, { "cell_type": "markdown", "metadata": { "id": "RCZ-eWicTmVx" }, "source": [ "Now the `Project.toml` and `Manifest.toml` files were created:" ] }, { "cell_type": "code", "execution_count": 339, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "CPbncHi09yFJ", "outputId": "b2a7c6bd-dce4-4e4b-f96b-23076a7f612e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "my_project\n", "my_project/Project.toml\n", "my_project/Manifest.toml\n" ] } ], "source": [ ";find my_project" ] }, { "cell_type": "markdown", "metadata": { "id": "Jw-VCU41Ua7A" }, "source": [ "Notice that the packages we added to the project were _not_ placed in the `my_project` directory itself. They were saved in the `~/.julia/packages` directory, the compiled files were placed in `~/.julia/compiled` director, logs were written to `~/.julia/logs` and so on.\n", "\n", "If several projects use the same package, it will only be downloaded and built once (well, once per version). The `~/.julia/packages` directory can hold multiple versions of the same package, so it's fine if different projects use different versions of the same package. There will be no conflict, no \"dependency hell\".\n" ] }, { "cell_type": "markdown", "metadata": { "id": "o9yjyihUisIK" }, "source": [ "The `Project.toml` just says that the project depends on `PyCall` and `Example`, and it specifies the UUID of this package:" ] }, { "cell_type": "code", "execution_count": 340, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Gz8KscpIhIX4", "outputId": "acb2dcd7-75ae-443f-b8d3-9357093e0f9b" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[deps]\n", "Example = \"7876af07-990d-54b4-ab0e-23690620f79a\"\n", "PyCall = \"438e738f-606a-5dbb-bf0a-cddfbfd45ab0\"\n" ] } ], "source": [ "print(read(\"my_project/Project.toml\", String))" ] }, { "cell_type": "markdown", "metadata": { "id": "IpxulMt32SFf" }, "source": [ "UUIDs are useful to avoid name conflicts. If several people name their package `CoolStuff`, then the UUID will clarify which one we are referring to." ] }, { "cell_type": "markdown", "metadata": { "id": "-_mbX8HUi7Qg" }, "source": [ "The `Manifest.toml` file is much longer, since it contains all the packages which `PyCall` and `Example` depend on, along with their versions (except for the standard library packages), and the dependency graph. This file should never be modified manually:\n" ] }, { "cell_type": "code", "execution_count": 341, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "28qXSoSwhOgm", "outputId": "4cff1a18-8a5d-445c-ec39-962c06f904de" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "# This file is machine-generated - editing it directly is not advised\n", "\n", "julia_version = \"1.11.5\"\n", "manifest_format = \"2.0\"\n", "project_hash = \"a4c0ea484367d5308a288ac3bbb28ae05c93b2ac\"\n", "\n", "[[deps.ArgTools]]\n", "uuid = \"0dad84c5-d112-42e6-8d28-ef12dabb789f\"\n", "version = \"1.1.2\"\n", "\n", "[[deps.Artifacts]]\n", "uuid = \"56f22d72-fd6d-98f1-02f0-08ddc0907c33\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.CompilerSupportLibraries_jll]]\n", "deps = [\"Artifacts\", \"Libdl\"]\n", "uuid = \"e66e0078-7015-5450-92f7-15fbd957f2ae\"\n", "version = \"1.1.1+0\"\n", "\n", "[[deps.Conda]]\n", "deps = [\"Downloads\", \"JSON\", \"VersionParsing\"]\n", "git-tree-sha1 = \"b19db3927f0db4151cb86d073689f2428e524576\"\n", "uuid = \"8f4d0f93-b110-5947-807f-2305c1781a2d\"\n", "version = \"1.10.2\"\n", "\n", "[[deps.Dates]]\n", "deps = [\"Printf\"]\n", "uuid = \"ade2ca70-3891-5945-98fb-dc099432e06a\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.Downloads]]\n", "deps = [\"ArgTools\", \"FileWatching\", \"LibCURL\", \"NetworkOptions\"]\n", "uuid = \"f43a241f-c20a-4ad4-852c-f6b1247861c6\"\n", "version = \"1.6.0\"\n", "\n", "[[deps.Example]]\n", "git-tree-sha1 = \"276fa06109ac5c80035cff711b0a18ad5b3117cc\"\n", "uuid = \"7876af07-990d-54b4-ab0e-23690620f79a\"\n", "version = \"0.3.3\"\n", "\n", "[[deps.FileWatching]]\n", "uuid = \"7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.JSON]]\n", "deps = [\"Dates\", \"Mmap\", \"Parsers\", \"Unicode\"]\n", "git-tree-sha1 = \"31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a\"\n", "uuid = \"682c06a0-de6a-54ab-a142-c8b1cf79cde6\"\n", "version = \"0.21.4\"\n", "\n", "[[deps.LibCURL]]\n", "deps = [\"LibCURL_jll\", \"MozillaCACerts_jll\"]\n", "uuid = \"b27032c2-a3e7-50c8-80cd-2d36dbcbfd21\"\n", "version = \"0.6.4\"\n", "\n", "[[deps.LibCURL_jll]]\n", "deps = [\"Artifacts\", \"LibSSH2_jll\", \"Libdl\", \"MbedTLS_jll\", \"Zlib_jll\", \"nghttp2_jll\"]\n", "uuid = \"deac9b47-8bc7-5906-a0fe-35ac56dc84c0\"\n", "version = \"8.6.0+0\"\n", "\n", "[[deps.LibSSH2_jll]]\n", "deps = [\"Artifacts\", \"Libdl\", \"MbedTLS_jll\"]\n", "uuid = \"29816b5a-b9ab-546f-933c-edad1886dfa8\"\n", "version = \"1.11.0+1\"\n", "\n", "[[deps.Libdl]]\n", "uuid = \"8f399da3-3557-5675-b5ff-fb832c97cbdb\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.LinearAlgebra]]\n", "deps = [\"Libdl\", \"OpenBLAS_jll\", \"libblastrampoline_jll\"]\n", "uuid = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.MacroTools]]\n", "git-tree-sha1 = \"1e0228a030642014fe5cfe68c2c0a818f9e3f522\"\n", "uuid = \"1914dd2f-81c6-5fcd-8719-6d5c9610ff09\"\n", "version = \"0.5.16\"\n", "\n", "[[deps.MbedTLS_jll]]\n", "deps = [\"Artifacts\", \"Libdl\"]\n", "uuid = \"c8ffd9c3-330d-5841-b78e-0817d7145fa1\"\n", "version = \"2.28.6+0\"\n", "\n", "[[deps.Mmap]]\n", "uuid = \"a63ad114-7e13-5084-954f-fe012c677804\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.MozillaCACerts_jll]]\n", "uuid = \"14a3606d-f60d-562e-9121-12d972cd8159\"\n", "version = \"2023.12.12\"\n", "\n", "[[deps.NetworkOptions]]\n", "uuid = \"ca575930-c2e3-43a9-ace4-1e988b2c1908\"\n", "version = \"1.2.0\"\n", "\n", "[[deps.OpenBLAS_jll]]\n", "deps = [\"Artifacts\", \"CompilerSupportLibraries_jll\", \"Libdl\"]\n", "uuid = \"4536629a-c528-5b80-bd46-f80d51c5b363\"\n", "version = \"0.3.27+1\"\n", "\n", "[[deps.Parsers]]\n", "deps = [\"Dates\", \"PrecompileTools\", \"UUIDs\"]\n", "git-tree-sha1 = \"7d2f8f21da5db6a806faf7b9b292296da42b2810\"\n", "uuid = \"69de0a69-1ddd-5017-9359-2bf0b02dc9f0\"\n", "version = \"2.8.3\"\n", "\n", "[[deps.PrecompileTools]]\n", "deps = [\"Preferences\"]\n", "git-tree-sha1 = \"5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f\"\n", "uuid = \"aea7be01-6a6a-4083-8856-8a6e6704d82a\"\n", "version = \"1.2.1\"\n", "\n", "[[deps.Preferences]]\n", "deps = [\"TOML\"]\n", "git-tree-sha1 = \"0f27480397253da18fe2c12a4ba4eb9eb208bf3d\"\n", "uuid = \"21216c6a-2e73-6563-6e65-726566657250\"\n", "version = \"1.5.0\"\n", "\n", "[[deps.Printf]]\n", "deps = [\"Unicode\"]\n", "uuid = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.PyCall]]\n", "deps = [\"Conda\", \"Dates\", \"Libdl\", \"LinearAlgebra\", \"MacroTools\", \"Serialization\", \"VersionParsing\"]\n", "git-tree-sha1 = \"9816a3826b0ebf49ab4926e2b18842ad8b5c8f04\"\n", "uuid = \"438e738f-606a-5dbb-bf0a-cddfbfd45ab0\"\n", "version = \"1.96.4\"\n", "\n", "[[deps.Random]]\n", "deps = [\"SHA\"]\n", "uuid = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.SHA]]\n", "uuid = \"ea8e919c-243c-51af-8825-aaa63cd721ce\"\n", "version = \"0.7.0\"\n", "\n", "[[deps.Serialization]]\n", "uuid = \"9e88b42a-f829-5b0c-bbe9-9e923198166b\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.TOML]]\n", "deps = [\"Dates\"]\n", "uuid = \"fa267f1f-6049-4f14-aa54-33bafae1ed76\"\n", "version = \"1.0.3\"\n", "\n", "[[deps.UUIDs]]\n", "deps = [\"Random\", \"SHA\"]\n", "uuid = \"cf7118a7-6976-5b1a-9a39-7adc72f591a4\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.Unicode]]\n", "uuid = \"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5\"\n", "version = \"1.11.0\"\n", "\n", "[[deps.VersionParsing]]\n", "git-tree-sha1 = \"58d6e80b4ee071f5efd07fda82cb9fbe17200868\"\n", "uuid = \"81def892-9a0e-5fdd-b105-ffc91e053289\"\n", "version = \"1.3.0\"\n", "\n", "[[deps.Zlib_jll]]\n", "deps = [\"Libdl\"]\n", "uuid = \"83775a58-1f1d-513f-b197-d71354ab007a\"\n", "version = \"1.2.13+1\"\n", "\n", "[[deps.libblastrampoline_jll]]\n", "deps = [\"Artifacts\", \"Libdl\"]\n", "uuid = \"8e850b90-86db-534c-a0d3-1478176c7d93\"\n", "version = \"5.11.0+0\"\n", "\n", "[[deps.nghttp2_jll]]\n", "deps = [\"Artifacts\", \"Libdl\"]\n", "uuid = \"8e850ede-7688-5339-a07c-302acd2aaf8d\"\n", "version = \"1.59.0+0\"\n" ] } ], "source": [ "print(read(\"my_project/Manifest.toml\", String))" ] }, { "cell_type": "markdown", "metadata": { "id": "P1JJXmuz1LPl" }, "source": [ "Note that `Manifest.toml` contains the precise version of the `Example` package that was installed, but the `Project.toml` file does not specify that version `0.3` is required. That's because Julia cannot know whether your project is supposed to work only with any version `0.3.x`, or whether it could work with other versions as well. So if you want to specify a version constraint for the `Example` package, you must add it manually in `Project.toml`. You would normally use your favorite editor to do this, but in this notebook we'll update `Project.toml` programmatically:" ] }, { "cell_type": "code", "execution_count": 342, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tgddc3M_1iyG", "outputId": "7c430b46-6af2-4c60-f7ab-cacfde376a83" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "26" ] }, "metadata": {}, "execution_count": 342 } ], "source": [ "append_config = \"\"\"\n", "\n", "[compat]\n", "Example = \"0.3\"\n", "\"\"\"\n", "\n", "open(f->write(f, append_config), \"my_project/Project.toml\", \"a\")" ] }, { "cell_type": "markdown", "metadata": { "id": "BD4rgzAT2GGs" }, "source": [ "Here is the updated `Project.toml` file:" ] }, { "cell_type": "code", "execution_count": 343, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XNAC8q0H17mI", "outputId": "ec54f471-a312-40d8-8696-ad9a9cf1b963" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[deps]\n", "Example = \"7876af07-990d-54b4-ab0e-23690620f79a\"\n", "PyCall = \"438e738f-606a-5dbb-bf0a-cddfbfd45ab0\"\n", "\n", "[compat]\n", "Example = \"0.3\"\n" ] } ], "source": [ "print(read(\"my_project/Project.toml\", String))" ] }, { "cell_type": "markdown", "metadata": { "id": "VyWAJoEq6GZs" }, "source": [ "Now if we try to replace `Example` 0.3 with version 0.2, we get an error:" ] }, { "cell_type": "code", "execution_count": 344, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "qgBa7Y0x6NUm", "outputId": "3d7fd27c-c001-4408-cafd-44a7bdb6678c" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "Pkg.Resolve.ResolverError(\"empty intersection between Example@0.2 and project compatibility 0.3\", nothing)" ] }, "metadata": {}, "execution_count": 344 } ], "source": [ "try\n", " pkg\"add Example@0.2\"\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "R3zvTUWSH4Sa" }, "source": [ "Now you can run a program based on this project, and it will have the possibility to use all the packages which have been added to this project, with their specific versions. If you import a package which was not explicitly added to this project, Julia will fallback to the default project:" ] }, { "cell_type": "code", "execution_count": 345, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XnSjhJSyISzT", "outputId": "eb15e418-531e-4132-fe86-4fd8066c4b59" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "117" ] }, "metadata": {}, "execution_count": 345 } ], "source": [ "code = \"\"\"\n", "import PyCall # found in the project\n", "import PyPlot # not found, so falls back to default project\n", "println(\"Success!\")\n", "\"\"\"\n", "\n", "open(f->write(f, code), \"my_program3.jl\", \"w\")" ] }, { "cell_type": "code", "execution_count": 346, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eM4yXtogIc5d", "outputId": "f616a076-d79d-4cf7-fceb-5e9de7ba5836" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Success!\n" ] } ], "source": [ ";julia --project=my_project my_program3.jl" ] }, { "cell_type": "markdown", "metadata": { "id": "U3C_z11CDXzJ" }, "source": [ "## Packages\n", "Falling back to the default project is fine, as long as you run the code on your own machine, but if you want to share your code with other people, it would be brittle to count on packages installed in _their_ default project. Instead, if you plan to share your code, you should clearly specify which packages it depends on, and use only these packages. Such a shareable project is called a **package**." ] }, { "cell_type": "markdown", "metadata": { "id": "7cLqsDTlMRM9" }, "source": [ "A package is a regular project (as defined above), but with a few extras:\n", "* the `Project.toml` file must specify a `name`, a `version` and a `uuid`.\n", "* there must be a `src/PackageName.jl` file containing a module named `PackageName`.\n", "* you generally want to specify the `authors` and `description`, and maybe also the `license`, `repository` (e.g., the package's github URL), and some `keywords`, but all of these are optional.\n", "\n", "It is very easy to create a new package using the `]generate` command. To define the `authors` field, `Pkg` will look up the `user.name` and `user.email` git config entries, so let's define them before we generate the package:" ] }, { "cell_type": "code", "execution_count": 347, "metadata": { "id": "-lrw9Diey5Jw" }, "outputs": [], "source": [ ";git config --global user.name \"Alice Bob\"" ] }, { "cell_type": "code", "execution_count": 348, "metadata": { "id": "jHImoacOzHyR" }, "outputs": [], "source": [ ";git config --global user.email \"alice.bob@example.com\"" ] }, { "cell_type": "code", "execution_count": 349, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bTw_L-4-ls4H", "outputId": "373be670-9d60-45ab-e3aa-b06a6075feed" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Generating\u001b[22m\u001b[39m project Hello:\n", " MyPackages/Hello/Project.toml\n", " MyPackages/Hello/src/Hello.jl\n" ] } ], "source": [ "]generate MyPackages/Hello" ] }, { "cell_type": "markdown", "metadata": { "id": "_OvnM3tBpW9X" }, "source": [ "This generated the `MyPackages/Hello/Project.toml` file (along with the enclosing directories) and the `MyPackages/Hello/src/Hello.jl` file. Let's take a look at the `Project.toml` file:" ] }, { "cell_type": "code", "execution_count": 350, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wQqVtTh5qkhm", "outputId": "554b8109-dc01-4129-96cc-e2d0faab2e4e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "name = \"Hello\"\n", "uuid = \"f5fd85bb-ea0c-4917-a7c1-fa0842e52507\"\n", "authors = [\"Alice Bob \"]\n", "version = \"0.1.0\"\n" ] } ], "source": [ "print(read(\"MyPackages/Hello/Project.toml\", String))" ] }, { "cell_type": "markdown", "metadata": { "id": "j1IL7fuyiAtb" }, "source": [ "Notice that the project has no dependencies yet, but it has a name, a unique UUID, and a version (plus an author)." ] }, { "cell_type": "markdown", "metadata": { "id": "Pgs4xop1qr1z" }, "source": [ "Note: if `Pkg` does not find a your name or email in the git config, it falls back to environment variables (`GIT_AUTHOR_NAME`, `GIT_COMMITTER_NAME`, `USER`, `USERNAME`, `NAME` and `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_EMAIL`, `EMAIL`)." ] }, { "cell_type": "markdown", "metadata": { "id": "VSyvL2-I0S_t" }, "source": [ "And let's look at the `src/Hello.jl` file:" ] }, { "cell_type": "code", "execution_count": 351, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Y4DvJfrC0W_u", "outputId": "fdc85f21-e0be-438d-b27e-7766cb75e253" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "module Hello\n", "\n", "greet() = print(\"Hello World!\")\n", "\n", "end # module Hello\n" ] } ], "source": [ "print(read(\"MyPackages/Hello/src/Hello.jl\", String))" ] }, { "cell_type": "markdown", "metadata": { "id": "6orcK-WH1dv8" }, "source": [ "Let's try to use the `greet()` function from the `Hello` package:" ] }, { "cell_type": "code", "execution_count": 352, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9ASQUXed1jR9", "outputId": "1f710b49-45bf-406f-bfa7-080950b07bf9" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "ArgumentError(\"Package Hello not found in current path.\\n- Run `import Pkg; Pkg.add(\\\"Hello\\\")` to install the Hello package.\")" ] }, "metadata": {}, "execution_count": 352 } ], "source": [ "try\n", " import Hello\n", " Hello.greet()\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "Ehdlz0d51pRY" }, "source": [ "Julia could not find the `Hello` package. When you're working on a package, don't forget to activate it first!" ] }, { "cell_type": "code", "execution_count": 353, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "geOkTHsh10es", "outputId": "ad659b9f-ac16-414a-ea74-ae7b62788999" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Activating\u001b[22m\u001b[39m project at `/content/MyPackages/Hello`\n" ] } ], "source": [ "]activate MyPackages/Hello" ] }, { "cell_type": "code", "execution_count": 354, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "aN3bg65b137-", "outputId": "44e4b853-968a-415a-b92f-315ea0c745c5" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[36m\u001b[1m[ \u001b[22m\u001b[39m\u001b[36m\u001b[1mInfo: \u001b[22m\u001b[39mPrecompiling Hello [f5fd85bb-ea0c-4917-a7c1-fa0842e52507] \n" ] }, { "output_type": "stream", "name": "stdout", "text": [ "Hello World!" ] } ], "source": [ "import Hello\n", "Hello.greet()" ] }, { "cell_type": "markdown", "metadata": { "id": "WxXCSYwIE9Eo" }, "source": [ "It works!\n", "\n", "If the `Hello` package depends on other packages, we must add them:" ] }, { "cell_type": "code", "execution_count": 355, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cZfLcJO3-m9D", "outputId": "ab64efb8-5ba0-4cab-978c-33decb6faf58" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Example ─ v0.5.5\n", "\u001b[32m\u001b[1m Compat\u001b[22m\u001b[39m entries added for PyCall, Example\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5\u001b[39m\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Manifest.toml`\n", " \u001b[90m[8f4d0f93] \u001b[39m\u001b[92m+ Conda v1.10.2\u001b[39m\n", " \u001b[90m[7876af07] \u001b[39m\u001b[92m+ Example v0.5.5\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[682c06a0] \u001b[39m\u001b[92m+ JSON v0.21.4\u001b[39m\n", " \u001b[90m[1914dd2f] \u001b[39m\u001b[92m+ MacroTools v0.5.16\u001b[39m\n", " \u001b[90m[69de0a69] \u001b[39m\u001b[92m+ Parsers v2.8.3\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[aea7be01] \u001b[39m\u001b[92m+ PrecompileTools v1.2.1\u001b[39m\n", " \u001b[90m[21216c6a] \u001b[39m\u001b[92m+ Preferences v1.5.0\u001b[39m\n", " \u001b[90m[438e738f] \u001b[39m\u001b[92m+ PyCall v1.96.4\u001b[39m\n", " \u001b[90m[81def892] \u001b[39m\u001b[92m+ VersionParsing v1.3.0\u001b[39m\n", " \u001b[90m[0dad84c5] \u001b[39m\u001b[92m+ ArgTools v1.1.2\u001b[39m\n", " \u001b[90m[56f22d72] \u001b[39m\u001b[92m+ Artifacts v1.11.0\u001b[39m\n", " \u001b[90m[ade2ca70] \u001b[39m\u001b[92m+ Dates v1.11.0\u001b[39m\n", " \u001b[90m[f43a241f] \u001b[39m\u001b[92m+ Downloads v1.6.0\u001b[39m\n", " \u001b[90m[7b1f6079] \u001b[39m\u001b[92m+ FileWatching v1.11.0\u001b[39m\n", " \u001b[90m[b27032c2] \u001b[39m\u001b[92m+ LibCURL v0.6.4\u001b[39m\n", " \u001b[90m[8f399da3] \u001b[39m\u001b[92m+ Libdl v1.11.0\u001b[39m\n", " \u001b[90m[37e2e46d] \u001b[39m\u001b[92m+ LinearAlgebra v1.11.0\u001b[39m\n", " \u001b[90m[a63ad114] \u001b[39m\u001b[92m+ Mmap v1.11.0\u001b[39m\n", " \u001b[90m[ca575930] \u001b[39m\u001b[92m+ NetworkOptions v1.2.0\u001b[39m\n", " \u001b[90m[de0858da] \u001b[39m\u001b[92m+ Printf v1.11.0\u001b[39m\n", " \u001b[90m[9a3f8284] \u001b[39m\u001b[92m+ Random v1.11.0\u001b[39m\n", " \u001b[90m[ea8e919c] \u001b[39m\u001b[92m+ SHA v0.7.0\u001b[39m\n", " \u001b[90m[9e88b42a] \u001b[39m\u001b[92m+ Serialization v1.11.0\u001b[39m\n", " \u001b[90m[fa267f1f] \u001b[39m\u001b[92m+ TOML v1.0.3\u001b[39m\n", " \u001b[90m[cf7118a7] \u001b[39m\u001b[92m+ UUIDs v1.11.0\u001b[39m\n", " \u001b[90m[4ec0a83e] \u001b[39m\u001b[92m+ Unicode v1.11.0\u001b[39m\n", " \u001b[90m[e66e0078] \u001b[39m\u001b[92m+ CompilerSupportLibraries_jll v1.1.1+0\u001b[39m\n", " \u001b[90m[deac9b47] \u001b[39m\u001b[92m+ LibCURL_jll v8.6.0+0\u001b[39m\n", " \u001b[90m[29816b5a] \u001b[39m\u001b[92m+ LibSSH2_jll v1.11.0+1\u001b[39m\n", " \u001b[90m[c8ffd9c3] \u001b[39m\u001b[92m+ MbedTLS_jll v2.28.6+0\u001b[39m\n", " \u001b[90m[14a3606d] \u001b[39m\u001b[92m+ MozillaCACerts_jll v2023.12.12\u001b[39m\n", " \u001b[90m[4536629a] \u001b[39m\u001b[92m+ OpenBLAS_jll v0.3.27+1\u001b[39m\n", " \u001b[90m[83775a58] \u001b[39m\u001b[92m+ Zlib_jll v1.2.13+1\u001b[39m\n", " \u001b[90m[8e850b90] \u001b[39m\u001b[92m+ libblastrampoline_jll v5.11.0+0\u001b[39m\n", " \u001b[90m[8e850ede] \u001b[39m\u001b[92m+ nghttp2_jll v1.59.0+0\u001b[39m\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m Packages marked with \u001b[33m⌅\u001b[39m have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 334.5 ms\u001b[32m ✓ \u001b[39mExample\n", " 1 dependency successfully precompiled in 0 seconds. 27 already precompiled.\n" ] } ], "source": [ "]add PyCall Example" ] }, { "cell_type": "markdown", "metadata": { "id": "IcipxHQW91lp" }, "source": [ "You must not use any package which has not been added to the project. If you do, you will get a warning." ] }, { "cell_type": "markdown", "metadata": { "id": "9MyUldzLAFZn" }, "source": [ "Once you are happy with your package, you can deploy it to github (or anywhere else). Then you can add it to your own projects just like any other package.\n", "\n", "If you want to make your package available to the world via the official Julia registry, you just need to send a Pull Request to https://github.com/JuliaRegistries/General. However, it's highly recommended to automate this using the [Registrator.jl](https://github.com/JuliaRegistries/Registrator.jl) github app.\n", "\n", "If you want to use other registries (including private registries), check out [this page](https://julialang.github.io/Pkg.jl/v1.4/registries/#)." ] }, { "cell_type": "markdown", "metadata": { "id": "nNd3-22NIkfB" }, "source": [ "Also check out the [`PkgTemplate`](https://github.com/invenia/PkgTemplates.jl) package, which provides more sophisticated templates for creating new packages, for example with continuous integration, code coverage tests, etc." ] }, { "cell_type": "markdown", "metadata": { "id": "NeVc0WCoJ_yl" }, "source": [ "## Fixing Issues in a Dependency\n", "Sometimes you may run into an issue inside one of the packages your project depends on. When this happens, you can use `Pkg`'s `dev` command to fix the issue. For example, let's pretend the `Example` package has a bug:" ] }, { "cell_type": "code", "execution_count": 356, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "HwIRORxQKcxn", "outputId": "a0058f1a-7c2b-4c87-f86b-e659547be6f5" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Cloning\u001b[22m\u001b[39m git-repo `https://github.com/JuliaLang/Example.jl.git`\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 ⇒ v0.5.5 `~/.julia/dev/Example`\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 ⇒ v0.5.5 `~/.julia/dev/Example`\u001b[39m\n" ] } ], "source": [ "]dev Example" ] }, { "cell_type": "markdown", "metadata": { "id": "7e63oY5HKgVD" }, "source": [ "This command cloned the repo into `~/.julia/dev/Example`:" ] }, { "cell_type": "code", "execution_count": 357, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "WV77BYAPK-ff", "outputId": "272deeee-ab15-463c-aa6c-f9e5c76970ca" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "total 4\n", "drwxr-xr-x 7 root root 4096 Oct 18 08:34 Example\n" ] } ], "source": [ ";ls -l \"~/.julia/dev\"" ] }, { "cell_type": "markdown", "metadata": { "id": "pB4nAoM2MmlI" }, "source": [ "It also updated the `Hello` package's `Manifest.toml` file to ensure the package now uses the `Example` clone. You can see this using `]status`:" ] }, { "cell_type": "code", "execution_count": 358, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UldZNewDMYaf", "outputId": "a56770c9-84ab-463d-a718-61e73e32c0aa" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\u001b[36m\u001b[1mProject\u001b[22m\u001b[39m Hello v0.1.0\n", "\u001b[32m\u001b[1mStatus\u001b[22m\u001b[39m `/content/MyPackages/Hello/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39mExample v0.5.5 `~/.julia/dev/Example`\n", " \u001b[90m[438e738f] \u001b[39mPyCall v1.96.4\n" ] } ], "source": [ "]st" ] }, { "cell_type": "markdown", "metadata": { "id": "OY080wXKK85W" }, "source": [ "So you would now go ahead and edit the clone and fix the bug. Of course, you would also want to send a PR to the package's owners so the source package gets fixed. Once that happens, you can go back to the official `Example` package easily:" ] }, { "cell_type": "code", "execution_count": 359, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6Ju_ehk6K4fi", "outputId": "689d83f5-b1ce-4d96-f765-b3d43a5a8be4" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 `~/.julia/dev/Example` ⇒ v0.5.5\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/MyPackages/Hello/Manifest.toml`\n", " \u001b[90m[7876af07] \u001b[39m\u001b[93m~ Example v0.5.5 `~/.julia/dev/Example` ⇒ v0.5.5\u001b[39m\n" ] } ], "source": [ "]free Example" ] }, { "cell_type": "code", "execution_count": 360, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gZNtvkp0NdQt", "outputId": "9b7e69ed-2b6c-4adf-af9d-061fce8d4777" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\u001b[36m\u001b[1mProject\u001b[22m\u001b[39m Hello v0.1.0\n", "\u001b[32m\u001b[1mStatus\u001b[22m\u001b[39m `/content/MyPackages/Hello/Project.toml`\n", " \u001b[90m[7876af07] \u001b[39mExample v0.5.5\n", " \u001b[90m[438e738f] \u001b[39mPyCall v1.96.4\n" ] } ], "source": [ "]st" ] }, { "cell_type": "markdown", "metadata": { "id": "bjk0y4x4X3OC" }, "source": [ "## Instantiating a Project\n", "If you want to run someone else's project and you want to make sure you are using the exact same package versions, you can clone the project, and assuming it has a `Manifest.toml` file, you can activate the project and run `]instantiate` to install all the appropriate packages. For example, let's instantiate the `Registrator.jl` project:" ] }, { "cell_type": "code", "execution_count": 361, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ukq2x0oKYUzv", "outputId": "889604d9-1dbf-40bb-e8db-dc83a98a3b81" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "Cloning into 'Registrator.jl'...\n" ] } ], "source": [ ";git clone https://github.com/JuliaRegistries/Registrator.jl" ] }, { "cell_type": "code", "execution_count": 362, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eP8xWq-lYqIe", "outputId": "e3536d71-0395-4dfa-e92a-004ea5691614" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Activating\u001b[22m\u001b[39m project at `/content/Registrator.jl`\n" ] } ], "source": [ "]activate Registrator.jl" ] }, { "cell_type": "code", "execution_count": 363, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mWTuYYjPYtRV", "outputId": "43985213-5a65-4a0c-e1d2-cffbdc48bbd7" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General.toml`\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m AutoHashEquals ─ v0.2.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m TimeToLive ───── v0.3.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Mustache ─────── v1.0.21\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Mocking ──────── v0.8.1\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m JSON3 ────────── v1.14.3\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m RegistryTools ── v2.3.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m TimeZones ────── v1.22.1\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m TZJData ──────── v1.5.0+2025b\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Pidfile ──────── v1.3.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m AssetRegistry ── v0.1.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m GitForge ─────── v0.4.4\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m SodiumSeal ───── v0.1.1\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m StructTypes ──── v1.11.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Hiccup ───────── v0.2.2\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m GitHub ───────── v5.10.0\n", "\u001b[32m\u001b[1m Installed\u001b[22m\u001b[39m Mux ──────────── v1.0.2\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/Registrator.jl/Project.toml`\n", "\u001b[33m⌅\u001b[39m \u001b[90m[15f4f7f2] \u001b[39m\u001b[92m+ AutoHashEquals v0.2.0\u001b[39m\n", " \u001b[90m[8f6bce27] \u001b[39m\u001b[92m+ GitForge v0.4.4\u001b[39m\n", " \u001b[90m[bc5e4493] \u001b[39m\u001b[92m+ GitHub v5.10.0\u001b[39m\n", " \u001b[90m[cd3eb016] \u001b[39m\u001b[92m+ HTTP v1.10.19\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[682c06a0] \u001b[39m\u001b[92m+ JSON v0.21.4\u001b[39m\n", " \u001b[90m[739be429] \u001b[39m\u001b[92m+ MbedTLS v1.1.9\u001b[39m\n", " \u001b[90m[78c3b35d] \u001b[39m\u001b[92m+ Mocking v0.8.1\u001b[39m\n", " \u001b[90m[ffc61752] \u001b[39m\u001b[92m+ Mustache v1.0.21\u001b[39m\n", " \u001b[90m[a975b10e] \u001b[39m\u001b[92m+ Mux v1.0.2\u001b[39m\n", " \u001b[90m[d1eb7eb1] \u001b[39m\u001b[92m+ RegistryTools v2.3.0\u001b[39m\n", " \u001b[90m[37f0c46e] \u001b[39m\u001b[92m+ TimeToLive v0.3.0\u001b[39m\n", " \u001b[90m[5c2747f8] \u001b[39m\u001b[92m+ URIs v1.6.1\u001b[39m\n", " \u001b[90m[c2297ded] \u001b[39m\u001b[92m+ ZMQ v1.5.0\u001b[39m\n", " \u001b[90m[2a0f44e3] \u001b[39m\u001b[93m~ Base64 ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[ade2ca70] \u001b[39m\u001b[93m~ Dates ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[8ba89e20] \u001b[39m\u001b[93m~ Distributed ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[7b1f6079] \u001b[39m\u001b[93m~ FileWatching ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[76f85450] \u001b[39m\u001b[93m~ LibGit2 ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[56ddb016] \u001b[39m\u001b[93m~ Logging ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[44cfe95a] \u001b[39m\u001b[93m~ Pkg ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[9e88b42a] \u001b[39m\u001b[93m~ Serialization ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[6462fe0b] \u001b[39m\u001b[93m~ Sockets ⇒ v1.11.0\u001b[39m\n", " \u001b[90m[cf7118a7] \u001b[39m\u001b[93m~ UUIDs ⇒ v1.11.0\u001b[39m\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m `/content/Registrator.jl/Manifest.toml`\n", " \u001b[90m[bf4720bc] \u001b[39m\u001b[92m+ AssetRegistry v0.1.0\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[15f4f7f2] \u001b[39m\u001b[92m+ AutoHashEquals v0.2.0\u001b[39m\n", " \u001b[90m[d1d4a3ce] \u001b[39m\u001b[92m+ BitFlags v0.1.9\u001b[39m\n", " \u001b[90m[944b1d66] \u001b[39m\u001b[92m+ CodecZlib v0.7.8\u001b[39m\n", " \u001b[90m[34da2185] \u001b[39m\u001b[92m+ Compat v4.18.1\u001b[39m\n", " \u001b[90m[f0e56b4a] \u001b[39m\u001b[92m+ ConcurrentUtilities v2.5.0\u001b[39m\n", " \u001b[90m[9a962f9c] \u001b[39m\u001b[92m+ DataAPI v1.16.0\u001b[39m\n", " \u001b[90m[e2d170a0] \u001b[39m\u001b[92m+ DataValueInterfaces v1.0.0\u001b[39m\n", " \u001b[90m[460bff9d] \u001b[39m\u001b[92m+ ExceptionUnwrapping v0.1.11\u001b[39m\n", " \u001b[90m[e2ba6199] \u001b[39m\u001b[92m+ ExprTools v0.1.10\u001b[39m\n", " \u001b[90m[8f6bce27] \u001b[39m\u001b[92m+ GitForge v0.4.4\u001b[39m\n", " \u001b[90m[bc5e4493] \u001b[39m\u001b[92m+ GitHub v5.10.0\u001b[39m\n", " \u001b[90m[cd3eb016] \u001b[39m\u001b[92m+ HTTP v1.10.19\u001b[39m\n", " \u001b[90m[9fb69e20] \u001b[39m\u001b[92m+ Hiccup v0.2.2\u001b[39m\n", " \u001b[90m[842dd82b] \u001b[39m\u001b[92m+ InlineStrings v1.4.5\u001b[39m\n", " \u001b[90m[82899510] \u001b[39m\u001b[92m+ IteratorInterfaceExtensions v1.0.0\u001b[39m\n", " \u001b[90m[692b3bcd] \u001b[39m\u001b[92m+ JLLWrappers v1.7.1\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[682c06a0] \u001b[39m\u001b[92m+ JSON v0.21.4\u001b[39m\n", " \u001b[90m[0f8b85d8] \u001b[39m\u001b[92m+ JSON3 v1.14.3\u001b[39m\n", " \u001b[90m[e6f89c97] \u001b[39m\u001b[92m+ LoggingExtras v1.2.0\u001b[39m\n", " \u001b[90m[1914dd2f] \u001b[39m\u001b[92m+ MacroTools v0.5.16\u001b[39m\n", " \u001b[90m[739be429] \u001b[39m\u001b[92m+ MbedTLS v1.1.9\u001b[39m\n", " \u001b[90m[78c3b35d] \u001b[39m\u001b[92m+ Mocking v0.8.1\u001b[39m\n", " \u001b[90m[ffc61752] \u001b[39m\u001b[92m+ Mustache v1.0.21\u001b[39m\n", " \u001b[90m[a975b10e] \u001b[39m\u001b[92m+ Mux v1.0.2\u001b[39m\n", " \u001b[90m[4d8831e6] \u001b[39m\u001b[92m+ OpenSSL v1.5.0\u001b[39m\n", " \u001b[90m[bac558e1] \u001b[39m\u001b[92m+ OrderedCollections v1.8.1\u001b[39m\n", " \u001b[90m[69de0a69] \u001b[39m\u001b[92m+ Parsers v2.8.3\u001b[39m\n", " \u001b[90m[fa939f87] \u001b[39m\u001b[92m+ Pidfile v1.3.0\u001b[39m\n", "\u001b[33m⌅\u001b[39m \u001b[90m[aea7be01] \u001b[39m\u001b[92m+ PrecompileTools v1.2.1\u001b[39m\n", " \u001b[90m[21216c6a] \u001b[39m\u001b[92m+ Preferences v1.5.0\u001b[39m\n", " \u001b[90m[d1eb7eb1] \u001b[39m\u001b[92m+ RegistryTools v2.3.0\u001b[39m\n", " \u001b[90m[6c6a2e73] \u001b[39m\u001b[92m+ Scratch v1.3.0\u001b[39m\n", " \u001b[90m[777ac1f9] \u001b[39m\u001b[92m+ SimpleBufferStream v1.2.0\u001b[39m\n", " \u001b[90m[2133526b] \u001b[39m\u001b[92m+ SodiumSeal v0.1.1\u001b[39m\n", " \u001b[90m[856f2bd8] \u001b[39m\u001b[92m+ StructTypes v1.11.0\u001b[39m\n", " \u001b[90m[dc5dba14] \u001b[39m\u001b[92m+ TZJData v1.5.0+2025b\u001b[39m\n", " \u001b[90m[3783bdb8] \u001b[39m\u001b[92m+ TableTraits v1.0.1\u001b[39m\n", " \u001b[90m[bd369af6] \u001b[39m\u001b[92m+ Tables v1.12.1\u001b[39m\n", " \u001b[90m[37f0c46e] \u001b[39m\u001b[92m+ TimeToLive v0.3.0\u001b[39m\n", " \u001b[90m[f269a46b] \u001b[39m\u001b[92m+ TimeZones v1.22.1\u001b[39m\n", " \u001b[90m[3bb67fe8] \u001b[39m\u001b[92m+ TranscodingStreams v0.11.3\u001b[39m\n", " \u001b[90m[5c2747f8] \u001b[39m\u001b[92m+ URIs v1.6.1\u001b[39m\n", " \u001b[90m[c2297ded] \u001b[39m\u001b[92m+ ZMQ v1.5.0\u001b[39m\n", " \u001b[90m[458c3c95] \u001b[39m\u001b[92m+ OpenSSL_jll v3.5.4+0\u001b[39m\n", " \u001b[90m[8f1865be] \u001b[39m\u001b[92m+ ZeroMQ_jll v4.3.6+0\u001b[39m\n", " \u001b[90m[a9144af2] \u001b[39m\u001b[92m+ libsodium_jll v1.0.21+0\u001b[39m\n", " \u001b[90m[0dad84c5] \u001b[39m\u001b[92m+ ArgTools v1.1.2\u001b[39m\n", " \u001b[90m[56f22d72] \u001b[39m\u001b[92m+ Artifacts v1.11.0\u001b[39m\n", " \u001b[90m[2a0f44e3] \u001b[39m\u001b[92m+ Base64 v1.11.0\u001b[39m\n", " \u001b[90m[ade2ca70] \u001b[39m\u001b[92m+ Dates v1.11.0\u001b[39m\n", " \u001b[90m[8ba89e20] \u001b[39m\u001b[92m+ Distributed v1.11.0\u001b[39m\n", " \u001b[90m[f43a241f] \u001b[39m\u001b[92m+ Downloads v1.6.0\u001b[39m\n", " \u001b[90m[7b1f6079] \u001b[39m\u001b[92m+ FileWatching v1.11.0\u001b[39m\n", " \u001b[90m[b77e0a4c] \u001b[39m\u001b[92m+ InteractiveUtils v1.11.0\u001b[39m\n", " \u001b[90m[b27032c2] \u001b[39m\u001b[92m+ LibCURL v0.6.4\u001b[39m\n", " \u001b[90m[76f85450] \u001b[39m\u001b[92m+ LibGit2 v1.11.0\u001b[39m\n", " \u001b[90m[8f399da3] \u001b[39m\u001b[92m+ Libdl v1.11.0\u001b[39m\n", " \u001b[90m[56ddb016] \u001b[39m\u001b[92m+ Logging v1.11.0\u001b[39m\n", " \u001b[90m[d6f4376e] \u001b[39m\u001b[92m+ Markdown v1.11.0\u001b[39m\n", " \u001b[90m[a63ad114] \u001b[39m\u001b[92m+ Mmap v1.11.0\u001b[39m\n", " \u001b[90m[ca575930] \u001b[39m\u001b[92m+ NetworkOptions v1.2.0\u001b[39m\n", " \u001b[90m[44cfe95a] \u001b[39m\u001b[92m+ Pkg v1.11.0\u001b[39m\n", " \u001b[90m[de0858da] \u001b[39m\u001b[92m+ Printf v1.11.0\u001b[39m\n", " \u001b[90m[9a3f8284] \u001b[39m\u001b[92m+ Random v1.11.0\u001b[39m\n", " \u001b[90m[ea8e919c] \u001b[39m\u001b[92m+ SHA v0.7.0\u001b[39m\n", " \u001b[90m[9e88b42a] \u001b[39m\u001b[92m+ Serialization v1.11.0\u001b[39m\n", " \u001b[90m[6462fe0b] \u001b[39m\u001b[92m+ Sockets v1.11.0\u001b[39m\n", " \u001b[90m[fa267f1f] \u001b[39m\u001b[92m+ TOML v1.0.3\u001b[39m\n", " \u001b[90m[a4e569a6] \u001b[39m\u001b[92m+ Tar v1.10.0\u001b[39m\n", " \u001b[90m[8dfed614] \u001b[39m\u001b[92m+ Test v1.11.0\u001b[39m\n", " \u001b[90m[cf7118a7] \u001b[39m\u001b[92m+ UUIDs v1.11.0\u001b[39m\n", " \u001b[90m[4ec0a83e] \u001b[39m\u001b[92m+ Unicode v1.11.0\u001b[39m\n", " \u001b[90m[deac9b47] \u001b[39m\u001b[92m+ LibCURL_jll v8.6.0+0\u001b[39m\n", " \u001b[90m[e37daf67] \u001b[39m\u001b[92m+ LibGit2_jll v1.7.2+0\u001b[39m\n", " \u001b[90m[29816b5a] \u001b[39m\u001b[92m+ LibSSH2_jll v1.11.0+1\u001b[39m\n", " \u001b[90m[c8ffd9c3] \u001b[39m\u001b[92m+ MbedTLS_jll v2.28.6+0\u001b[39m\n", " \u001b[90m[14a3606d] \u001b[39m\u001b[92m+ MozillaCACerts_jll v2023.12.12\u001b[39m\n", " \u001b[90m[83775a58] \u001b[39m\u001b[92m+ Zlib_jll v1.2.13+1\u001b[39m\n", " \u001b[90m[8e850ede] \u001b[39m\u001b[92m+ nghttp2_jll v1.59.0+0\u001b[39m\n", " \u001b[90m[3f19e933] \u001b[39m\u001b[92m+ p7zip_jll v17.4.0+2\u001b[39m\n", "\u001b[36m\u001b[1m Info\u001b[22m\u001b[39m Packages marked with \u001b[33m⌅\u001b[39m have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`\n", "\u001b[92m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", " 570.2 ms\u001b[32m ✓ \u001b[39mAutoHashEquals\n", " 1007.4 ms\u001b[32m ✓ \u001b[39mTimeToLive\n", " 1145.6 ms\u001b[32m ✓ \u001b[39m\u001b[90mTZJData\u001b[39m\n", " 1530.9 ms\u001b[32m ✓ \u001b[39m\u001b[90mPidfile\u001b[39m\n", " 2052.0 ms\u001b[32m ✓ \u001b[39mMustache\n", " 2869.3 ms\u001b[32m ✓ \u001b[39m\u001b[90mStructTypes\u001b[39m\n", " 1031.6 ms\u001b[32m ✓ \u001b[39mMocking\n", " 1772.9 ms\u001b[32m ✓ \u001b[39m\u001b[90mHiccup\u001b[39m\n", " 1388.8 ms\u001b[32m ✓ \u001b[39m\u001b[90mSodiumSeal\u001b[39m\n", " 1157.3 ms\u001b[32m ✓ \u001b[39m\u001b[90mAssetRegistry\u001b[39m\n", " 3183.4 ms\u001b[32m ✓ \u001b[39mRegistryTools\n", " 5552.9 ms\u001b[32m ✓ \u001b[39mGitHub\n", " 5689.4 ms\u001b[32m ✓ \u001b[39mMux\n", " 19007.2 ms\u001b[32m ✓ \u001b[39m\u001b[90mTimeZones\u001b[39m\n", " 21304.3 ms\u001b[32m ✓ \u001b[39m\u001b[90mJSON3\u001b[39m\n", " 3401.9 ms\u001b[32m ✓ \u001b[39mGitForge\n", " 3162.4 ms\u001b[32m ✓ \u001b[39mRegistrator\n", " 17 dependencies successfully precompiled in 33 seconds. 60 already precompiled.\n" ] } ], "source": [ "]instantiate" ] }, { "cell_type": "markdown", "metadata": { "id": "UoMg99bTtFDp" }, "source": [ "Usually, that's all you need to know about projects and packages, but let's look at bit under the hood, so you can handle less common cases." ] }, { "cell_type": "markdown", "metadata": { "id": "V8Y6A_IuepPI" }, "source": [ "## Load Path\n", "When you import a package, Julia searches for it in the environments listed in the `LOAD_PATH` array. An **environment** can be a project or a directory containing a bunch of packages directly. By default, the `LOAD_PATH` array contains three elements:" ] }, { "cell_type": "code", "execution_count": 364, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wpSWi2epgdpk", "outputId": "b21eb2d6-2efe-4a73-e1dc-d079315f9eff" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{String}:\n", " \"@\"\n", " \"@v#.#\"\n", " \"@stdlib\"" ] }, "metadata": {}, "execution_count": 364 } ], "source": [ "LOAD_PATH" ] }, { "cell_type": "markdown", "metadata": { "id": "hH-S7BL9iuN4" }, "source": [ "Here's what these elements mean:\n", "* `\"@\"` represents the active project, if any: that's the project activated via `--project`, `JULIA_PROJECT`, `]activate` or `Pkg.activate()`.\n", "* `\"@v#.#\"` represents the default shared project for the version of Julia we are running. That's why it is used by default when there is no active project.\n", "* `\"@stdlib\"` represents the standard library. This is not a project: it's a directory containing many packages." ] }, { "cell_type": "markdown", "metadata": { "id": "QHbG_HsOP8O7" }, "source": [ "If you want to see the actual paths, you can call `Base.load_path()`:" ] }, { "cell_type": "code", "execution_count": 365, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oGya8XxwQDJz", "outputId": "96759e22-f0b5-4864-d9c0-a8b90d6f4bcf" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{String}:\n", " \"/content/Registrator.jl/Project.toml\"\n", " \"/root/.julia/environments/v1.11/Project.toml\"\n", " \"/usr/local/share/julia/stdlib/v1.11\"" ] }, "metadata": {}, "execution_count": 365 } ], "source": [ "Base.load_path()" ] }, { "cell_type": "markdown", "metadata": { "id": "ZaIw9vRxLVVt" }, "source": [ "You can change the load path if you want to. For example, if you want Julia to look only in the active project and in the standard library, without looking in the default project, then you can set the `JULIA_LOAD_PATH` environment variable to `\"@:@stdlib\"`." ] }, { "cell_type": "markdown", "metadata": { "id": "8EScSOrdb3ha" }, "source": [ "If you try to run `my_program3.jl` this way, it will successfully import `PyCall`, but it will fail to import `PyPlot`, since it is not listed in `Project.toml` (however, it would successfully import any package from the standard library):" ] }, { "cell_type": "code", "execution_count": 366, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sN2NunpdLgKR", "outputId": "69c59911-dbae-4b26-8ced-141748c24232" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "ERROR: LoadError: ArgumentError: Package PyPlot not found in current path.\n", "- Run `import Pkg; Pkg.add(\"PyPlot\")` to install the PyPlot package.\n", "Stacktrace:\n", " [1] macro expansion\n", " @ ./loading.jl:2296 [inlined]\n", " [2] macro expansion\n", " @ ./lock.jl:273 [inlined]\n", " [3] __require(into::Module, mod::Symbol)\n", " @ Base ./loading.jl:2271\n", " [4] #invoke_in_world#3\n", " @ ./essentials.jl:1089 [inlined]\n", " [5] invoke_in_world\n", " @ ./essentials.jl:1086 [inlined]\n", " [6] require(into::Module, mod::Symbol)\n", " @ Base ./loading.jl:2260\n", "in expression starting at /content/my_program3.jl:2\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "ProcessFailedException(Base.Process[Process(`\u001b[4mjulia\u001b[24m \u001b[4m--project=my_project\u001b[24m \u001b[4mmy_program3.jl\u001b[24m`, ProcessExited(1))])" ] }, "metadata": {}, "execution_count": 366 } ], "source": [ "try\n", " withenv(\"JULIA_LOAD_PATH\"=>\"@:@stdlib\", \"LD_PRELOAD\"=>\"\") do\n", " run(`julia --project=my_project my_program3.jl`)\n", " end\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "FrLpiVS2UG9S" }, "source": [ "You can also modify the `LOAD_PATH` array programmatically, for example to make all the packages in the `my_packages/` directory available to the project:" ] }, { "cell_type": "code", "execution_count": 367, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "8kGSkqsVULfC", "outputId": "dcb500f7-ef6f-4e84-c636-5f0d03d161cd" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{String}:\n", " \"@\"\n", " \"@v#.#\"\n", " \"@stdlib\"\n", " \"my_packages\"" ] }, "metadata": {}, "execution_count": 367 } ], "source": [ "push!(LOAD_PATH, \"my_packages\")" ] }, { "cell_type": "markdown", "metadata": { "id": "1_fpeCNoRARJ" }, "source": [ "Now any package added to this directory will be directly available to us:" ] }, { "cell_type": "code", "execution_count": 368, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3C42rrfKQ01y", "outputId": "b090f9ba-30b1-439a-990f-c84f0a93f9a4" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[32m\u001b[1m Generating\u001b[22m\u001b[39m project Hello2:\n", " my_packages/Hello2/Project.toml\n", " my_packages/Hello2/src/Hello2.jl\n" ] } ], "source": [ "]generate my_packages/Hello2" ] }, { "cell_type": "code", "execution_count": 369, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ClgC-rhWQ9XP", "outputId": "8c325ab3-1626-4658-bbf1-483f3c35bb7c" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[36m\u001b[1m[ \u001b[22m\u001b[39m\u001b[36m\u001b[1mInfo: \u001b[22m\u001b[39mPrecompiling Hello2 [a952cb70-f232-429f-a988-7379953cea39] \n" ] }, { "output_type": "stream", "name": "stdout", "text": [ "Hello World!" ] } ], "source": [ "using Hello2\n", "Hello2.greet()" ] }, { "cell_type": "markdown", "metadata": { "id": "-kkX-WKARP0l" }, "source": [ "This is a convenience for development, as we didn't have to push this package to a repository or even add it to the project. However, it's just for development: once you're happy with your package, make sure to push it to a repo, and add it to the project normally." ] }, { "cell_type": "markdown", "metadata": { "id": "C1V5pYziAzWG" }, "source": [ "## Depots\n", "As we saw earlier, new packages you add to a project are placed in the `~/.julia/packages` directory, logs are placed in `~/.julia/logs`, and so on.\n", "\n", "A directory like `~/.julia` which contains `Pkg` related content is called a **depot**. Julia installs all new packages in the default depot, which is the first directory in the `DEPOT_PATH` array (this array can be modified manually in Julia, or set via the `JULIA_DEPOT_PATH` environment variable):" ] }, { "cell_type": "code", "execution_count": 370, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "r4y3Uarp-VXi", "outputId": "3f4e09b8-a715-4991-d80a-0512c1790f14" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "3-element Vector{String}:\n", " \"/root/.julia\"\n", " \"/usr/local/local/share/julia\"\n", " \"/usr/local/share/julia\"" ] }, "metadata": {}, "execution_count": 370 } ], "source": [ "DEPOT_PATH" ] }, { "cell_type": "markdown", "metadata": { "id": "pg_JLf9R-geB" }, "source": [ "The default depot needs to be writeable for the current user, since that's where new packages will be written to (as well as logs and other stuff). The other depots can be read-only: they're typically used for private package registries." ] }, { "cell_type": "markdown", "metadata": { "id": "lAJBZg_RXUqj" }, "source": [ "You can occasionally run the `]gc` command, which will remove all unused package versions (`Pkg` will use the logs to located existing projects)." ] }, { "cell_type": "markdown", "metadata": { "id": "1uhHgsN2WUG_" }, "source": [ "In summary: when some code runs `using Foo` or `import Foo`, the `LOAD_PATH` is used to determine _which_ specific package `Foo` refers to, while the `DEPOT_PATH` is used to determine _where_ it is. The exception is when the `LOAD_PATH` contains directories which directly contain packages: for these packages, the `DEPOT_PATH` is not used." ] }, { "cell_type": "markdown", "metadata": { "id": "jgeH4A06IIX4" }, "source": [ "# Parallel Computing\n", "Julia supports coroutines (aka green threads), multithreading (without a [GIL](https://en.wikipedia.org/wiki/Global_interpreter_lock#:~:text=A%20global%20interpreter%20lock%20(GIL,on%20a%20multi%2Dcore%20processor.) like CPython!), multiprocessing and distributed computing." ] }, { "cell_type": "markdown", "metadata": { "id": "oVCHhzbYFUv5" }, "source": [ "## Coroutines\n", "Let's go back to the `fibonacci()` generator function:" ] }, { "cell_type": "code", "execution_count": 371, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "LHQ6czbxItEw", "outputId": "0901a849-57d3-4636-9019-ab2e5b92cba9" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n" ] } ], "source": [ "function fibonacci(n)\n", " Channel() do ch\n", " a, b = 1, 1\n", " for i in 1:n\n", " put!(ch, a)\n", " a, b = b, a + b\n", " end\n", " end\n", "end\n", "\n", "for f in fibonacci(10)\n", " println(f)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "mgDS2AjiVljr" }, "source": [ "Under the hood, `Channel() do ... end` creates a `Channel` object, and spawns an asynchronous `Task` to execute the code in the `do ... end` block. The task is scheduled to execute immediately, but when it calls the `put!()` function on the channel to yield a value, it blocks until another task calls the `take!()` function to grab that value. You do not see the `take!()` function explicitly in this code example, since it is executed automatically in the `for` loop, in the main task. To demonstrate this, we can just call the `take!()` function 10 times to get all the items from the channel:" ] }, { "cell_type": "code", "execution_count": 372, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-tff7DpqbK-J", "outputId": "4c6d89b5-ad7a-4c29-e342-863ebfd0d198" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n" ] } ], "source": [ "ch = fibonacci(10)\n", "for i in 1:10\n", " println(take!(ch))\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "pUZjr1wHdc5y" }, "source": [ "This channel is bound to the task, therefore it is automatically closed when the task ends. So if we try to get one more element, we will get an exception:" ] }, { "cell_type": "code", "execution_count": 373, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9eJjrbrHdU33", "outputId": "04bec09c-f972-4c84-8416-2881e33e06eb" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "InvalidStateException(\"Channel is closed.\", :closed)" ] }, "metadata": {}, "execution_count": 373 } ], "source": [ "try\n", " take!(ch)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "VLvvfxZznXps" }, "source": [ "Here is a more explicit version of the `fibonacci()` function:" ] }, { "cell_type": "code", "execution_count": 374, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FSP1vZjheZDJ", "outputId": "54b3973e-7e03-481c-d3fa-9769a37d334d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "fibonacci (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 374 } ], "source": [ "function fibonacci(n)\n", " function generator_func(ch, n)\n", " a, b = 1, 1\n", " for i in 1:n\n", " put!(ch, a)\n", " a, b = b, a + b\n", " end\n", " end\n", " ch = Channel()\n", " task = @task generator_func(ch, n) # creates a task without starting it\n", " bind(ch, task) # the channel will be closed when the task ends\n", " schedule(task) # start running the task asynchronously\n", " ch\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "-KnzQbUGonIJ" }, "source": [ "And here is a more explicit version of the `for` loop:" ] }, { "cell_type": "code", "execution_count": 375, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ir3FUJAEoD5j", "outputId": "f286f356-e88a-439f-b612-0bf5fb9a66d4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n" ] } ], "source": [ "ch = fibonacci(10)\n", "for value in ch\n", " println(value)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "YTwMO7gxpCSa" }, "source": [ "Note that asynchronous tasks (also called \"coroutines\" or \"green threads\") are not actually run in parallel: they cooperate to alternate execution. Some functions, such as `put!()`, `take!()`, and many I/O functions, interrupt the current task's execution, at which point it lets Julia's scheduler decide which task should resume its execution. This is just like Python's coroutines." ] }, { "cell_type": "markdown", "metadata": { "id": "yTiiI6ZfbKKQ" }, "source": [ "For more details on coroutines and tasks, see [the manual](https://docs.julialang.org/en/v1/manual/control-flow/#man-tasks-1)." ] }, { "cell_type": "markdown", "metadata": { "id": "N9dzuKC2qFRL" }, "source": [ "## Multithreading\n", "Julia also supports multithreading. You can specify the number of O.S. threads upon startup, by setting the `JULIA_NUM_THREADS` environment variable (or setting the `-t` argument in Julia 1.5+)." ] }, { "cell_type": "code", "execution_count": 376, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mBYUWpQpDBzS", "outputId": "ce1a9f19-d6b1-4ccb-dc64-c2eabf4dc622" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"auto\"" ] }, "metadata": {}, "execution_count": 376 } ], "source": [ "ENV[\"JULIA_NUM_THREADS\"]" ] }, { "cell_type": "markdown", "metadata": { "id": "8AK8aRLqEhJt" }, "source": [ "The actual number of threads started by Julia may be lower than that, as it is limited to the number of available cores on the machine (thanks to hyperthreading, each physical core may run two threads). Here is the number of threads that were actually started:" ] }, { "cell_type": "code", "execution_count": 377, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KjEv4FOwEQhl", "outputId": "2322b407-0baa-48c6-bc95-ee0506bc330f" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2" ] }, "metadata": {}, "execution_count": 377 } ], "source": [ "using Base.Threads\n", "nthreads()" ] }, { "cell_type": "markdown", "metadata": { "id": "tc3bXp57DINZ" }, "source": [ "Now let's run 10 tasks across these threads:" ] }, { "cell_type": "code", "execution_count": 378, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ic1ecJ-1qatN", "outputId": "bb30a5c3-73a9-46a6-ec46-29196baf9b2d" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "thread #1 is starting task #1\n", "thread #2 is starting task #6\n", "thread #2 is finished\n", "thread #2 is starting task #7\n", "thread #1 is finished\n", "thread #1 is starting task #2\n", "thread #2 is finished\n", "thread #2 is starting task #8\n", "thread #1 is finished\n", "thread #2 is starting task #3\n", "thread #1 is finished\n", "thread #2 is starting task #9\n", "thread #1 is finished\n", "thread #2 is starting task #4\n", "thread #2 is finished\n", "thread #2 is starting task #5\n", "thread #2 is finished\n", "thread #1 is finished\n", "thread #2 is starting task #10\n", "thread #2 is finished\n" ] } ], "source": [ "@threads for i in 1:10\n", " println(\"thread #\", threadid(), \" is starting task #$i\")\n", " sleep(rand()) # pretend we're actually working\n", " println(\"thread #\", threadid(), \" is finished\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "SHpLG3h7LVMm" }, "source": [ "Here is a multithreaded version of the `estimate_pi()` function. Each thread computes part of the sum, and the parts are added at the end:" ] }, { "cell_type": "code", "execution_count": 379, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mDHzyNqdJswG", "outputId": "2ea9fadd-6ef2-4382-a85b-2b11de9f8efb" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 126.719 ms (16 allocations: 1.22 KiB)\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "3.1415926635894196" ] }, "metadata": {}, "execution_count": 379 } ], "source": [ "function parallel_estimate_pi(n)\n", " s = zeros(nthreads())\n", " nt = n ÷ nthreads()\n", " @threads for t in 1:nthreads()\n", " for i in (1:nt) .+ nt*(t - 1)\n", " @inbounds s[t] += (isodd(i) ? -1 : 1) / (2i + 1)\n", " end\n", " end\n", " return 4.0 * (1.0 + sum(s))\n", "end\n", "\n", "@btime parallel_estimate_pi(100_000_000)" ] }, { "cell_type": "markdown", "metadata": { "id": "3Ql0KLRNSqNg" }, "source": [ "The `@inbounds` macro is an optimization: it tells the Julia compiler not to add any bounds check when accessing the array. It's safe in this case since the `s` array has one element per thread, and `t` varies from `1` to `nthreads()`, so there is no risk for `s[t]` to be out of bounds." ] }, { "cell_type": "markdown", "metadata": { "id": "tVftN7gXQCOh" }, "source": [ "Let's compare this with the single-threaded implementation:" ] }, { "cell_type": "code", "execution_count": 380, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "F2Yfz67kK2xX", "outputId": "bf6dbfa1-b095-4247-b6d2-c5ac62ce4c8a" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 126.156 ms (0 allocations: 0 bytes)\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "3.141592663589326" ] }, "metadata": {}, "execution_count": 380 } ], "source": [ "@btime estimate_pi(100_000_000)" ] }, { "cell_type": "markdown", "metadata": { "id": "BnOKCxlNQX01" }, "source": [ "If you are running this notebook on Colab, the parallel implementation is probably no faster than the single-threaded one. That's because the Colab Runtime only has a single CPU, so there is no benefit from multithreading (plus there is a bit of overhead for managing threads). However, on my 8-core machine, using 16 threads, the parallel implementation is about 6 times faster than the single-threaded one." ] }, { "cell_type": "markdown", "metadata": { "id": "nOUeRI6DRbmo" }, "source": [ "Julia has a `mapreduce()` function which makes it easy to implement functions like `parallel_estimate_pi()`:" ] }, { "cell_type": "code", "execution_count": 381, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "c04i5fsvO5m5", "outputId": "a2faf0c0-0c41-48c7-85b0-c9539147f70d" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "parallel_estimate_pi2 (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 381 } ], "source": [ "function parallel_estimate_pi2(n)\n", " 4.0 * mapreduce(i -> (isodd(i) ? -1 : 1) / (2i + 1), +, 0:n)\n", "end" ] }, { "cell_type": "code", "execution_count": 382, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ZHKduTA-PW5T", "outputId": "015040da-fc48-4dad-b524-536a534d579e" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 65.007 ms (0 allocations: 0 bytes)\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "3.141592663589793" ] }, "metadata": {}, "execution_count": 382 } ], "source": [ "@btime parallel_estimate_pi2(100_000_000)" ] }, { "cell_type": "markdown", "metadata": { "id": "1Lwb_H7vTeCg" }, "source": [ "The `mapreduce()` function is well optimized, so it's about twice as fast as `parallel_estimate_pi()`." ] }, { "cell_type": "markdown", "metadata": { "id": "g0PXPosbamtW" }, "source": [ "You can also spawn a task using `Threads.@spawn`. It will get executed on any one of the running threads (it will not start a new thread):" ] }, { "cell_type": "code", "execution_count": 383, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "w2YYrw3IYX4v", "outputId": "c9a14c1e-3521-4101-f3ea-93c7163d6339" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hello!\n", "Thread starting\n", "Thread stopping\n", "The result is: 42\n" ] } ], "source": [ "task = Threads.@spawn begin\n", " println(\"Thread starting\")\n", " sleep(1)\n", " println(\"Thread stopping\")\n", " 42 # result\n", "end\n", "\n", "println(\"Hello!\")\n", "\n", "println(\"The result is: \", fetch(task))\n" ] }, { "cell_type": "markdown", "metadata": { "id": "pbv40Ib7bF7v" }, "source": [ "The `fetch()` function waits for the thread to finish, and fetches the result. You can also just call `wait()` if you don't need the result." ] }, { "cell_type": "markdown", "metadata": { "id": "OsK9bt-Hb2IS" }, "source": [ "Last but not least, you can use channels to synchronize and communicate across tasks, even if they are running across separate threads:" ] }, { "cell_type": "code", "execution_count": 384, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "sgdNzgkaceEz", "outputId": "b1cac217-7c14-4db1-e9b3-96e753e728de" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Received 1\n", "Received 4\n", "Received 9\n", "Received 16\n", "Finished sending!\n", "Received 25\n", "Finished receiving!\n" ] } ], "source": [ "ch = Channel()\n", "task1 = Threads.@spawn begin\n", " for i in 1:5\n", " sleep(rand())\n", " put!(ch, i^2)\n", " end\n", " println(\"Finished sending!\")\n", " close(ch)\n", "end\n", "\n", "task2 = Threads.@spawn begin\n", " foreach(v->println(\"Received $v\"), ch)\n", " println(\"Finished receiving!\")\n", "end\n", "\n", "wait(task2)" ] }, { "cell_type": "markdown", "metadata": { "id": "eLOdUrTcV1Lh" }, "source": [ "For more details about multithreading, check out [this page](https://docs.julialang.org/en/v1/manual/parallel-computing/#man-multithreading-1)." ] }, { "cell_type": "markdown", "metadata": { "id": "0JfyJjTXX1AM" }, "source": [ "## Multiprocessing & Distributed Programming\n", "Julia can spawn multiple Julia processes upon startup if you specify the number of processes via the `-p` argument. You can also spawn extra processes from Julia itself:" ] }, { "cell_type": "code", "execution_count": 385, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "JtqG4qIhX5Sw", "outputId": "a936ac08-f1ac-44d5-901e-dc1ecde67759" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 2\n", " 3\n", " 4\n", " 5" ] }, "metadata": {}, "execution_count": 385 } ], "source": [ "using Distributed\n", "addprocs(4)\n", "workers() # array of worker process ids" ] }, { "cell_type": "markdown", "metadata": { "id": "WD9hcmhGfraw" }, "source": [ "The main process has id 1:" ] }, { "cell_type": "code", "execution_count": 386, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "TlsjSMFZe3Ae", "outputId": "ae7159b2-5e7a-487a-d085-0613935df898" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1" ] }, "metadata": {}, "execution_count": 386 } ], "source": [ "myid()" ] }, { "cell_type": "markdown", "metadata": { "id": "lAS3Q8ohfvwg" }, "source": [ "The `@everywhere` macro lets you run any code on all workers:" ] }, { "cell_type": "code", "execution_count": 387, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "lmhHLhtbenY5", "outputId": "ab0460f8-403a-4d3a-8c0a-34e60118609d" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi! I'm worker 1\n", " From worker 4:\tHi! I'm worker 4\n", " From worker 3:\tHi! I'm worker 3\n", " From worker 2:\tHi! I'm worker 2\n", " From worker 5:\tHi! I'm worker 5\n" ] } ], "source": [ "@everywhere println(\"Hi! I'm worker $(myid())\")" ] }, { "cell_type": "markdown", "metadata": { "id": "cyQQ0Td2hAuQ" }, "source": [ "You can also execute code on a particular worker by using `@spawnat `:\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 388, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Mmc1-zfBgsur", "outputId": "e5587249-61e8-4c50-a1ae-4a13db44ec2a" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Future(3, 1, 14, ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), SpinLock(0)), (46, 0, -1)), nothing)" ] }, "metadata": {}, "execution_count": 388 } ], "source": [ "@spawnat 3 println(\"Hi! I'm worker $(myid())\")" ] }, { "cell_type": "markdown", "metadata": { "id": "nZaV4ptbk4sB" }, "source": [ "If you specify `:any` instead of a worker id, Julia chooses the worker for you:" ] }, { "cell_type": "code", "execution_count": 389, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "i6C7ydoBkyln", "outputId": "1ddf69d0-137f-4af2-8dde-b2a96f1f3ffa" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Future(2, 1, 15, ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), SpinLock(0)), (139267361548128, 139267510191424, 139267510189264)), nothing)" ] }, "metadata": {}, "execution_count": 389 } ], "source": [ "@spawnat :any println(\"Hi! I'm worker $(myid())\")" ] }, { "cell_type": "markdown", "metadata": { "id": "fZMDIATZg9Ug" }, "source": [ "The output of `@spawnat` is a `Future` object. You can call `fetch()` on this object to wait for the result:" ] }, { "cell_type": "code", "execution_count": 390, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Ml5TBZLQf2oY", "outputId": "1859f7a5-0bfe-4c7f-8b82-32f6540c59ee" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " From worker 3:\tHi! I'm worker 3\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "10" ] }, "metadata": {}, "execution_count": 390 } ], "source": [ "result = @spawnat 3 1+2+3+4\n", "fetch(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "f8rR6jBdhuvT" }, "source": [ "If you import some package in the main process, it is not automatically imported in the workers. For example, the following code fails because the worker does not know what `pyimport` is:" ] }, { "cell_type": "code", "execution_count": 391, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "s89GQtv5iNfP", "outputId": "925c2b1e-3348-4c12-ad1b-00affe3833a6" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " From worker 2:\tHi! I'm worker 2\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "RemoteException(4, CapturedException(UndefVarError(:pyimport, Main), Any[(#118 at macros.jl:83, 1), (#invokelatest#2 at essentials.jl:1055 [inlined], 1), (invokelatest at essentials.jl:1052, 1), (#107 at process_messages.jl:283, 1), (run_work_thunk at process_messages.jl:70, 1), (run_work_thunk at process_messages.jl:79, 1), (#100 at process_messages.jl:88, 1)]))" ] }, "metadata": {}, "execution_count": 391 } ], "source": [ "using PyCall\n", "\n", "result = @spawnat 4 (np = pyimport(\"numpy\"); np.log(10))\n", "\n", "try\n", " fetch(result)\n", "catch ex\n", " ex\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "N-wFnpu7inDg" }, "source": [ "You must use `@everywhere` or `@spawnat` to import the packages you need in each worker:" ] }, { "cell_type": "code", "execution_count": 392, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eseDsHvzna1R", "outputId": "1746e4e7-1f1d-4f5b-bf78-23d12d1eb507" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "2.302585092994046" ] }, "metadata": {}, "execution_count": 392 } ], "source": [ "@everywhere using PyCall\n", "\n", "result = @spawnat 4 (np = pyimport(\"numpy\"); np.log(10))\n", "\n", "fetch(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "Nzk3gbLOnqTx" }, "source": [ "Similarly, if you define a function in the main process, it is not automatically available in the workers. You must define the function in every worker:" ] }, { "cell_type": "code", "execution_count": 393, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vrvbWhDMnzdX", "outputId": "c5754a22-a209-4b6a-f78f-cbabbe469a7a" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "42" ] }, "metadata": {}, "execution_count": 393 } ], "source": [ "@everywhere addtwo(n) = n + 2\n", "result = @spawnat 4 addtwo(40)\n", "fetch(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "9skIo6pnoikr" }, "source": [ "You can pass a `Future` to `@everywhere` or `@spawnat`, as long as you wrap it in a `fetch()` function:" ] }, { "cell_type": "code", "execution_count": 394, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IjovCqk7n_cu", "outputId": "82852558-2eef-4def-c0e7-b0b91673d84c" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "5-element Vector{Float64}:\n", " 1.5598359450571153\n", " 9.77255628698287\n", " 5.983511443658351\n", " 8.084261249368886\n", " 9.873546141395915" ] }, "metadata": {}, "execution_count": 394 } ], "source": [ "M = @spawnat 2 rand(5)\n", "result = @spawnat 3 fetch(M) .* 10.0\n", "fetch(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "H4YeovYto60s" }, "source": [ "In this example, worker 2 creates a random array, then worker 3 fetches this array and multiplies each element by 10, then the main process fetches the result and displays it." ] }, { "cell_type": "markdown", "metadata": { "id": "nUl0Nj9dV-IW" }, "source": [ "## GPU\n", "Julia has excellent GPU support. As you may know, GPUs are devices which can run thousands of threads in parallel. Each thread is slower and more limited than on a CPU, but there are so many of them that plenty of tasks can be executed much faster on a GPU than on a CPU, provided these tasks can be parallelized.\n", "\n", "Let's check which GPU device is installed:" ] }, { "cell_type": "code", "execution_count": 395, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DTKc9IFB-eum", "outputId": "b2e8727f-a119-44b1-85c6-c06828ea5502" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Sat Oct 18 08:36:45 2025 \n", "+-----------------------------------------------------------------------------------------+\n", "| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |\n", "|-----------------------------------------+------------------------+----------------------+\n", "| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\n", "| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\n", "| | | MIG M. |\n", "|=========================================+========================+======================|\n", "| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n", "| N/A 48C P8 10W / 70W | 0MiB / 15360MiB | 0% Default |\n", "| | | N/A |\n", "+-----------------------------------------+------------------------+----------------------+\n", " \n", "+-----------------------------------------------------------------------------------------+\n", "| Processes: |\n", "| GPU GI CI PID Type Process name GPU Memory |\n", "| ID ID Usage |\n", "|=========================================================================================|\n", "| No running processes found |\n", "+-----------------------------------------------------------------------------------------+\n" ] } ], "source": [ ";nvidia-smi" ] }, { "cell_type": "markdown", "metadata": { "id": "ULS7Jn6vws2-" }, "source": [ "If you're running on the Colab, your runtime will generally have an Nvidia Tesla T4 16GB GPU, but others are sometimes available (especially if you have the Pro version).\n", "\n", "If no GPU is detected, go to _Runtime_ > _Change runtime type_, set _Hardware accelerator_ to _GPU_, then go to _Runtime_ > _Factory reset runtime_, then run the first cells to reinstall the required packages, and come back here. If you're running on your own machine, make sure you have a compatible GPU card installed, with the appropriate drivers." ] }, { "cell_type": "markdown", "metadata": { "id": "HzVnsDN--gRr" }, "source": [ "Now let's create a large matrix and time how long it takes to square it on the CPU:" ] }, { "cell_type": "code", "execution_count": 396, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-rbfON6gWYwe", "outputId": "e6e1733d-1be3-45d0-aa44-f3781293142a" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 246.308 ms (3 allocations: 32.00 MiB)\n" ] } ], "source": [ "using BenchmarkTools\n", "\n", "M = rand(2^11, 2^11)\n", "\n", "function benchmark_matmul_cpu(M)\n", " M * M\n", " return\n", "end\n", "\n", "benchmark_matmul_cpu(M) # warm up\n", "@btime benchmark_matmul_cpu($M)" ] }, { "cell_type": "markdown", "metadata": { "id": "LRO3dOh1lnVg" }, "source": [ "Notes:\n", "* For benchmarking, we wrapped the operation in a function which returns `nothing`.\n", "* Why do we have a \"warm up\" line? Well, since Julia compiles code on the fly the first time it is executed, it's good practice to execute the operation we want to benchmark at least once before starting the benchmark, or else the benchmark will include the compilation time.\n", "* We used `$M` instead of `M` on the last line. This is a feature of the `@btime` macro: it evaluates `M` before benchmarking takes place, to avoid the extra delay that is incurred when [benchmarking with global variables](https://docs.julialang.org/en/latest/manual/performance-tips/#Avoid-global-variables-1)." ] }, { "cell_type": "markdown", "source": [ "In Colab, GPU computation should happen only on the main process, not on remote workers, so before using CUDA, let's remove all extra workers:" ], "metadata": { "id": "RLsHEfdVOQC1" } }, { "cell_type": "code", "source": [ "rmprocs(workers())" ], "metadata": { "id": "TPKJVkEtN7bw", "outputId": "39656373-d0f8-4bb0-8e7b-91bcd84cddc7", "colab": { "base_uri": "https://localhost:8080/" } }, "execution_count": 397, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Task (done) @0x00007ea9544d5780" ] }, "metadata": {}, "execution_count": 397 } ] }, { "cell_type": "markdown", "metadata": { "id": "x-nbiuYDtGlb" }, "source": [ "Now let's benchmark this same operation on the GPU:" ] }, { "cell_type": "code", "execution_count": 398, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3hIWY1O_WE6a", "outputId": "cd2b65eb-becb-44df-94b6-0983af1285f3" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "\u001b[33m\u001b[1m┌ \u001b[22m\u001b[39m\u001b[33m\u001b[1mWarning: \u001b[22m\u001b[39mYou are using CUDA 12.5.0 with a driver for CUDA 13.x.\n", "\u001b[33m\u001b[1m│ \u001b[22m\u001b[39mIt is recommended to upgrade your driver, or switch to automatic installation of CUDA.\n", "\u001b[33m\u001b[1m└ \u001b[22m\u001b[39m\u001b[90m@ CUDA ~/.julia/packages/CUDA/G7Cnt/src/initialization.jl:137\u001b[39m\n" ] }, { "output_type": "stream", "name": "stdout", "text": [ " 2.431 ms (72 allocations: 1.64 KiB)\n" ] } ], "source": [ "using CUDA\n", "\n", "# Copy the data to the GPU. Creates a CuArray:\n", "M_on_gpu = cu(M)\n", "\n", "# Alternatively, create a new random matrix directly on the GPU:\n", "#M_on_gpu = CUDA.CURAND.rand(2^11, 2^11)\n", "\n", "function benchmark_matmul_gpu(M)\n", " CUDA.@sync M * M\n", " return\n", "end\n", "\n", "benchmark_matmul_gpu(M_on_gpu) # warm up\n", "@btime benchmark_matmul_gpu($M_on_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "jFEBz1hkZ6Ox" }, "source": [ "That's _much_ faster (over 100x faster in my test on Colab with an NVidia Tesla T4 GPU)." ] }, { "cell_type": "markdown", "metadata": { "id": "EbjxRKWc1-nR" }, "source": [ "Importantly:\n", "* Before the GPU can work on some data, it needs to be copied to the GPU (or generated there directly).\n", "* the `CUDA.@sync` macro waits for the GPU operation to complete. Without it, the operation would happen in parallel on the GPU, while execution would continue on the CPU. So we would just be timing how long it takes to _start_ the operation, not how long it takes to complete.\n", "* In general, you don't need `CUDA.@sync`, since many operations (including `cu()`) call it implicitly, and it's usually a good idea to let the CPU and GPU work in parallel. Typically, the GPU will be working on the current batch of data while the CPU works on preparing the next batch." ] }, { "cell_type": "markdown", "metadata": { "id": "lja576nUWXLU" }, "source": [ "Of course, the speed up will vary depending on the matrix size and the GPU type. Moreover, copying the data from the CPU to the GPU is often the slowest part of the operation, but we only benchmarked the matrix multiplication itself. Let's see what we get if we include the data transfer in the benchmark:" ] }, { "cell_type": "markdown", "metadata": { "id": "LMegVhMzBtPz" }, "source": [ "That's still much faster than on the CPU." ] }, { "cell_type": "markdown", "metadata": { "id": "WdvtwJAu3aOU" }, "source": [ "Let's check how much RAM we have left on the GPU:" ] }, { "cell_type": "code", "execution_count": 399, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "599aiP0s3c4y", "outputId": "db5f24a2-f547-4912-f356-f7323f0bb724" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Effective GPU memory usage: 50.59% (7.458 GiB/14.741 GiB)\n", "Memory pool usage: 4.828 GiB (7.344 GiB reserved)\n" ] } ], "source": [ "CUDA.memory_status()" ] }, { "cell_type": "markdown", "metadata": { "id": "86XHVDDrDJjp" }, "source": [ "Julia's Garbage Collector will free CUDA arrays like any other object, when there's no more reference to it. However, `CUDA.jl` uses a memory pool to make allocations faster on the GPU, so don't be surprised if the allocated memory on the GPU does not go down immediately. Moreover, IJulia keeps a reference to the output of each cell, so if you let any cell output a `CuArray`, it will only be released when you execute `Out[]=0`. If you want to force the Garbage Collector to run, you an run `GC.gc()`. To reclaim memory from the memory pool, use `CUDA.reclaim()`:" ] }, { "cell_type": "code", "execution_count": 400, "metadata": { "id": "VPoO6-h_tVBE" }, "outputs": [], "source": [ "GC.gc()\n", "CUDA.reclaim()" ] }, { "cell_type": "markdown", "metadata": { "id": "ndcNywPSE0D9" }, "source": [ "Many other operations are implemented for `CuArray` (`+`, `-`, etc.) and dotted operations (`.+`, `exp.()`, etc). Importantly, loop fusion also works on the GPU. For example, if we want to compute `M .* M .+ M`, without loop fusion the GPU would first compute `M .* M` and create a temporary array, then it would add `M` to that array, like this:" ] }, { "cell_type": "code", "execution_count": 401, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "psC2K-M_F0w9", "outputId": "fd7482f5-149e-4cf1-b424-92aedf3e2d46" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 422.275 μs (228 allocations: 6.34 KiB)\n" ] } ], "source": [ "function benchmark_without_fusion(M)\n", " P = M .* M\n", " CUDA.@sync P .+ M\n", " return\n", "end\n", "\n", "benchmark_without_fusion(M_on_gpu) # warm up\n", "@btime benchmark_without_fusion($M_on_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "DTbDFRdJIO9M" }, "source": [ "Instead, loop fusion ensures that the array is only traversed once, without the need for a temporary array:" ] }, { "cell_type": "code", "execution_count": 402, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "zHQI4nkHGkLx", "outputId": "a604b8af-1f1e-465b-8639-093589d8a638" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 219.786 μs (144 allocations: 3.86 KiB)\n" ] } ], "source": [ "function benchmark_with_fusion(M)\n", " CUDA.@sync M .* M .+ M\n", " return\n", "end\n", "\n", "benchmark_with_fusion(M_on_gpu) # warm up\n", "@btime benchmark_with_fusion($M_on_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "ln5b2NykQCsD" }, "source": [ "That's _much_ faster (about twice as fast in my test on Colab). 😃" ] }, { "cell_type": "markdown", "metadata": { "id": "9HAHfceIa3U5" }, "source": [ "Lastly, you can actually **write your own GPU kernels in Julia**! In other words, rather than using GPU operations implemented in the `CUDA.jl` package (or others), you can write Julia code that will be compiled for the GPU, and executed there. This can occasionally be useful to speed up some algorithms where the standard kernels don't suffice. For example, here's a GPU kernel which implements `u .+= v`, where `u` and `v` are two (large) vectors:" ] }, { "cell_type": "code", "execution_count": 403, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "lnbRTzgWX0gx", "outputId": "924c27e3-69b6-416a-beaf-6d810df43f02" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "gpu_add! (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 403 } ], "source": [ "function worker_gpu_add!(u, v)\n", " index = (blockIdx().x - 1) * blockDim().x + threadIdx().x\n", " index ≤ length(u) && (@inbounds u[index] += v[index])\n", " return\n", "end\n", "\n", "function gpu_add!(u, v)\n", " numblocks = ceil(Int, length(u) / 256)\n", " @cuda threads=256 blocks=numblocks worker_gpu_add!(u, v)\n", " return u\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "WEnX1i4ooQiN" }, "source": [ "This code example is adapted from the [`CUDA.jl` package's documentation](https://juliagpu.gitlab.io/CUDA.jl/tutorials/introduction/), which I highly encourage you to check out if you're interested in writing your own kernels. Here are the key parts to understand this example, starting from the end:\n", "* The `gpu_add!()` function first calculates `numblocks`, the number of blocks of threads to start, then it uses the `@cuda` macro to spawn `numblocks` blocks of GPU threads, each with 256 threads, and each thread runs `worker_gpu_add!(u, v)`.\n", "* The `worker_gpu_add!()` function computes `u[index] += v[index]` for a single value of `index`: in other words, each thread will just update a single value in the vector! Let's see how the index is computed:\n", " * The `@cuda` macro spawned many blocks of 256 threads each. These blocks are organized in a grid, which is one-dimensional by default, but it can be up to three-dimensional. Therefore each thread and each block have an `(x, y, z)` coordinate in this grid. See this diagram from the [Nvidia blog post](https://developer.nvidia.com/blog/even-easier-introduction-cuda/):
\n", ".\n", " * `threadIdx().x` returns the current GPU thread's `x` coordinate within its block (one difference with the diagram is that Julia is 1-indexed).\n", " * `blockIdx().x` returns the current block's `x` coordinate in the grid.\n", " * `blockDim().x` returns the block size along the `x` axis (in this example, it's 256).\n", " * `gridDim().x` returns the number of blocks in the grid, along the `x` axis (in this example it's `numblocks`).\n", " * So the `index` that each thread must update in the array is `(blockIdx().x - 1) * blockDim().x + threadIdx().x`.\n", "* As explained earlier, the `@inbounds` macro is an optimization that tells Julia that the index is guaranteed to be inbounds, so there's no need for it to check.\n", "\n", "Now writing your own GPU kernel won't seem like something only top experts with advanced C++ skills can do: you can do it too!" ] }, { "cell_type": "markdown", "metadata": { "id": "gf6-5tJXRCm0" }, "source": [ "Let's check that the kernel works as expected:" ] }, { "cell_type": "code", "execution_count": 404, "metadata": { "id": "l7mGsSWYL2iW" }, "outputs": [], "source": [ "u = rand(2^20)\n", "v = rand(2^20)\n", "\n", "u_on_gpu = cu(u)\n", "v_on_gpu = cu(v)\n", "\n", "u .+= v\n", "gpu_add!(u_on_gpu, v_on_gpu)\n", "\n", "@assert Array(u_on_gpu) ≈ u" ] }, { "cell_type": "markdown", "metadata": { "id": "GCZyhpbwNeLQ" }, "source": [ "Yes, it works well!\n", "\n", "Note: the `≈` operator checks whether the operands are approximately equal within the float precision limit." ] }, { "cell_type": "markdown", "metadata": { "id": "Koqs9GrqRHeW" }, "source": [ "Let's benchmark our custom kernel:" ] }, { "cell_type": "code", "execution_count": 405, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "g2_7SawNR4Jb", "outputId": "8781080d-2b24-433e-c2d7-a021fc3014ce" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 59.037 μs (12 allocations: 240 bytes)\n" ] } ], "source": [ "function benchmark_custom_assign_add!(u, v)\n", " CUDA.@sync gpu_add!(u, v)\n", " return\n", "end\n", "\n", "benchmark_custom_assign_add!(u_on_gpu, v_on_gpu)\n", "@btime benchmark_custom_assign_add!($u_on_gpu, $v_on_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "hl0_1Km9SyD7" }, "source": [ "Let's see how this compares to `CUDA.jl`'s implementation:" ] }, { "cell_type": "code", "execution_count": 406, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vogLKdtEO9l6", "outputId": "22e28d30-ae1e-453b-8441-0ca64d6ba6c7" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " 67.150 μs (86 allocations: 2.12 KiB)\n" ] } ], "source": [ "function benchmark_assign_add!(u, v)\n", " CUDA.@sync u .+= v\n", " return\n", "end\n", "\n", "benchmark_assign_add!(u_on_gpu, v_on_gpu)\n", "@btime benchmark_assign_add!($u_on_gpu, $v_on_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "IKRNLPIRcbCd" }, "source": [ "How about that? Our custom kernel is a bit faster than `CUDA.jl`'s kernel! But to be fair, our kernel would not work with huge vectors, since there's a limit to the number of blocks & threads you can spawn (see [Table 15](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#features-and-technical-specifications) in CUDA's documentation). To support such huge vectors, we need each worker to run a loop like this:" ] }, { "cell_type": "code", "execution_count": 407, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Zi8-UvGocWOK", "outputId": "62edeaa6-b52a-4489-a41d-5efce80ec810" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "worker_gpu_add! (generic function with 1 method)" ] }, "metadata": {}, "execution_count": 407 } ], "source": [ "function worker_gpu_add!(u, v)\n", " index = (blockIdx().x - 1) * blockDim().x + threadIdx().x\n", " stride = blockDim().x * gridDim().x\n", " for i = index:stride:length(u)\n", " @inbounds u[i] += v[i]\n", " end\n", " return\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "YtQNhlw1c3zo" }, "source": [ "This way, if `@cuda` is executed with a smaller number of blocks than needed to have one thread per array item, the workers will loop appropriately.\n", "\n", "This should get you started! For more info, check out [`CUDA.jl`'s documentation](https://cuda.juliagpu.org/stable/)." ] }, { "cell_type": "markdown", "metadata": { "id": "FA9DFoEuiGib" }, "source": [ "# Command Line Arguments" ] }, { "cell_type": "markdown", "metadata": { "id": "I1nlKvCZhVo7" }, "source": [ "Command line arguments are available via `ARGS`:\n", "\n" ] }, { "cell_type": "code", "execution_count": 408, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Ysg_6WElhY7-", "outputId": "066afc9f-2297-43ad-8166-0d992b697785" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "1-element Vector{String}:\n", " \"/root/.local/share/jupyter/runt\"\u001b[93m\u001b[1m ⋯ 21 bytes ⋯ \u001b[22m\u001b[39m\"0e2-4016-bd46-622109a66ce5.json\"" ] }, "metadata": {}, "execution_count": 408 } ], "source": [ "ARGS" ] }, { "cell_type": "markdown", "metadata": { "id": "jRe_1ovZhdqh" }, "source": [ "Unlike Python's `sys.argv`, the first element of this array is not the program name. If you need the program name, use `PROGRAM_FILE` instead:" ] }, { "cell_type": "code", "execution_count": 409, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2z3obYZghrHL", "outputId": "eb23dfc6-763f-46f2-dedc-b7e228852fd8" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "\"\"" ] }, "metadata": {}, "execution_count": 409 } ], "source": [ "PROGRAM_FILE" ] }, { "cell_type": "markdown", "metadata": { "id": "UnKMaPm8r1XC" }, "source": [ "You can get the current module, directory, file or line number:" ] }, { "cell_type": "code", "execution_count": 410, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ZIou0Kgbr5mu", "outputId": "57f320c8-935f-4246-f9c2-5510f7c967ad" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(Main, \"/content\", \"In[410]\", 1)" ] }, "metadata": {}, "execution_count": 410 } ], "source": [ "@__MODULE__, @__DIR__, @__FILE__, @__LINE__" ] }, { "cell_type": "markdown", "metadata": { "id": "ahpsLiV_hBLi" }, "source": [ "The equivalent of Python's `if __name__ == \"__main__\"` is:" ] }, { "cell_type": "code", "execution_count": 411, "metadata": { "id": "hPUUlZG6g_A5" }, "outputs": [], "source": [ "if abspath(PROGRAM_FILE) == @__FILE__\n", " println(\"Starting of the program\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "id": "8g43x6YYiKLf" }, "source": [ "# Memory Management" ] }, { "cell_type": "markdown", "metadata": { "id": "o-YehjM8iMmn" }, "source": [ "Let's check how many megabytes of RAM are available:" ] }, { "cell_type": "code", "execution_count": 412, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "O80YQtSciPvV", "outputId": "e90a79c7-95a9-4697-8ec7-a09352359867" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Available RAM: 11086 MB\n" ] } ], "source": [ "free() = println(\"Available RAM: \", Sys.free_memory() ÷ 10^6, \" MB\")\n", "\n", "free()" ] }, { "cell_type": "markdown", "metadata": { "id": "bxKvaTpfdO5j" }, "source": [ "If a variable holds a large object that you don't need anymore, you can either wait until the variable falls out of scope, or set it to `nothing`. Either way, the memory will only be freed when the Garbage Collector does its magic, which may not be immediate. In general, you don't have to worry about that, but if you want, you can always call the GC directly:" ] }, { "cell_type": "code", "execution_count": 413, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "j0M8Oka3fod6", "outputId": "c525acf2-7e38-4f26-c2cd-492ae1402fa7" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "sum(M)=5.0005098957713865e7\n", "sum(M)=5.0006538077012256e7\n" ] } ], "source": [ "function use_ram()\n", " M = rand(10000, 10000) # use 400+MB of RAM\n", " println(\"sum(M)=$(sum(M))\")\n", "end # M will be freed by the GC eventually after this\n", "\n", "use_ram()\n", "\n", "M = rand(10000, 10000) # use 400+MB of RAM\n", "println(\"sum(M)=$(sum(M))\")\n", "M = nothing\n", "\n", "GC.gc() # rarely needed" ] }, { "cell_type": "code", "execution_count": 414, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "O8Fbip9OpRyD", "outputId": "33ac8c44-8d05-4141-8336-ac75d1cc6542" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Available RAM: 11133 MB\n" ] } ], "source": [ "free()" ] }, { "cell_type": "markdown", "metadata": { "id": "EsYZBLwwpqEG" }, "source": [ "# Thanks!" ] }, { "cell_type": "markdown", "metadata": { "id": "0QOVcDKLptgh" }, "source": [ "I hope you enjoyed this introduction to Julia! I recommend you join the friendly and helpful Julia community on Slack or Discourse.\n", "\n", "Cheers!\n", "\n", "Aurélien Geron" ] } ], "metadata": { "accelerator": "GPU", "colab": { "provenance": [], "gpuType": "T4" }, "kernelspec": { "display_name": "Julia", "name": "julia" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia" } }, "nbformat": 4, "nbformat_minor": 0 }