From 9404b5482c94f46b5b295f2cd28fcead36b1505f Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 21:02:07 +0200 Subject: [PATCH 01/15] Update pattern matching docs - Move non pin related stuff to prev. section - Add section on pinned values for map matches to reference --- .../pages/getting-started/pattern-matching.md | 64 +++++++++---------- .../pages/references/patterns-and-guards.md | 13 ++++ 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/lib/elixir/pages/getting-started/pattern-matching.md b/lib/elixir/pages/getting-started/pattern-matching.md index 8b114791d53..78dde7c0ef2 100644 --- a/lib/elixir/pages/getting-started/pattern-matching.md +++ b/lib/elixir/pages/getting-started/pattern-matching.md @@ -113,6 +113,38 @@ iex> [0 | list] [0, 1, 2, 3] ``` +In some cases, you don't care about a particular value in a pattern. It is a common practice to bind those values to the underscore, `_`. For example, if only the head of the list matters to us, we can assign the tail to underscore: + +```elixir +iex> [head | _] = [1, 2, 3] +[1, 2, 3] +iex> head +1 +``` + +The variable `_` is special in that it can never be read from. Trying to read from it gives a compile error: + +```elixir +iex> _ +** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions +``` + +If a variable is mentioned more than once in a pattern, all references must bind to the same value: + +```elixir +iex> {x, x} = {1, 1} +{1, 1} +iex> {x, x} = {1, 2} +** (MatchError) no match of right hand side value: {1, 2} +``` + +Although pattern matching allows us to build powerful constructs, its usage is limited. For instance, you cannot make function calls on the left side of a match. The following example is invalid: + +```elixir +iex> length([1, [2], 3]) = 3 +** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match +``` + Pattern matching allows developers to easily destructure data types such as tuples and lists. As we will see in the following chapters, it is one of the foundations of recursion in Elixir and applies to other types as well, like maps and binaries. ## The pin operator @@ -168,36 +200,4 @@ iex> {y, 1} = {2, 2} ** (MatchError) no match of right hand side value: {2, 2} ``` -If a variable is mentioned more than once in a pattern, all references must bind to the same value: - -```elixir -iex> {x, x} = {1, 1} -{1, 1} -iex> {x, x} = {1, 2} -** (MatchError) no match of right hand side value: {1, 2} -``` - -In some cases, you don't care about a particular value in a pattern. It is a common practice to bind those values to the underscore, `_`. For example, if only the head of the list matters to us, we can assign the tail to underscore: - -```elixir -iex> [head | _] = [1, 2, 3] -[1, 2, 3] -iex> head -1 -``` - -The variable `_` is special in that it can never be read from. Trying to read from it gives a compile error: - -```elixir -iex> _ -** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions -``` - -Although pattern matching allows us to build powerful constructs, its usage is limited. For instance, you cannot make function calls on the left side of a match. The following example is invalid: - -```elixir -iex> length([1, [2], 3]) = 3 -** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match -``` - This finishes our introduction to pattern matching. As we will see in the next chapter, pattern matching is very common in many language constructs and they can be further augmented with guards. diff --git a/lib/elixir/pages/references/patterns-and-guards.md b/lib/elixir/pages/references/patterns-and-guards.md index 551fe29b6b1..2029a495ca4 100644 --- a/lib/elixir/pages/references/patterns-and-guards.md +++ b/lib/elixir/pages/references/patterns-and-guards.md @@ -83,6 +83,19 @@ iex> _ ** (CompileError) iex:3: invalid use of _ ``` +A pinned value represents the value itself and not its – even if syntatically equal – pattern. The right hand side is compared to be equal to the pinned value: + +```iex +iex> x = %{} +%{} +iex> {:ok, %{}} = {:ok, %{a: 13}} +{:ok, %{a: 13}} +iex> {:ok, ^x} = {:ok, %{a: 13}} +** (MatchError) no match of right hand side value: {:ok, %{a: 13}} + (stdlib 6.2) erl_eval.erl:667: :erl_eval.expr/6 + iex:2: (file) +``` + ### Literals (numbers and atoms) Atoms and numbers (integers and floats) can appear in patterns and they are always represented as is. For example, an atom will only match an atom if they are the same atom: From ea2c0639b184658c692b2c9dd60efe2542ea8a74 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 21:21:21 +0200 Subject: [PATCH 02/15] Update keyword list docs - Stick to optional syntax explained in the text --- lib/elixir/pages/getting-started/keywords-and-maps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/keywords-and-maps.md b/lib/elixir/pages/getting-started/keywords-and-maps.md index 040a47bedc8..353f09d83a6 100644 --- a/lib/elixir/pages/getting-started/keywords-and-maps.md +++ b/lib/elixir/pages/getting-started/keywords-and-maps.md @@ -131,7 +131,7 @@ iex> if true do In the example above, the `do` and `else` blocks make up a keyword list. They are nothing more than a syntax convenience on top of keyword lists. We can rewrite the above to: ```elixir -iex> if true, do: "This will be seen", else: "This won't" +iex> if(true, do: "This will be seen", else: "This won't") "This will be seen" ``` From 431e0070efa9d9422e9e9ba56d99d87697ba98f0 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 21:35:30 +0200 Subject: [PATCH 03/15] Add some more cross references --- lib/elixir/pages/getting-started/anonymous-functions.md | 2 +- lib/elixir/pages/getting-started/keywords-and-maps.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/anonymous-functions.md b/lib/elixir/pages/getting-started/anonymous-functions.md index bec912d000e..b8160ca34b2 100644 --- a/lib/elixir/pages/getting-started/anonymous-functions.md +++ b/lib/elixir/pages/getting-started/anonymous-functions.md @@ -9,7 +9,7 @@ Anonymous functions allow us to store and pass executable code around as if it w ## Identifying functions and documentation -Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions. +Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions – the functions defined on [modules](modules-and-functions.md). Functions in Elixir are identified by both their name and their arity. The arity of a function describes the number of arguments that the function takes. From this point on we will use both the function name and its arity to describe functions throughout the documentation. `trunc/1` identifies the function which is named `trunc` and takes `1` argument, whereas `trunc/2` identifies a different (nonexistent) function with the same name but with an arity of `2`. diff --git a/lib/elixir/pages/getting-started/keywords-and-maps.md b/lib/elixir/pages/getting-started/keywords-and-maps.md index 353f09d83a6..26889f634a2 100644 --- a/lib/elixir/pages/getting-started/keywords-and-maps.md +++ b/lib/elixir/pages/getting-started/keywords-and-maps.md @@ -225,6 +225,8 @@ These operations have one large benefit in that they raise if the key does not e Elixir developers typically prefer to use the `map.key` syntax and pattern matching instead of the functions in the `Map` module when working with maps because they lead to an assertive style of programming. [This blog post by José Valim](https://dashbit.co/blog/writing-assertive-code-with-elixir) provides insight and examples on how you get more concise and faster software by writing assertive code in Elixir. +In a further chapter you'll learn about ["Structs"](structs.md) in elixir, which further enforce the idea of a map with predefined keys. + ## Nested data structures Often we will have maps inside maps, or even keywords lists inside maps, and so forth. Elixir provides conveniences for manipulating nested data structures via the `get_in/1`, `put_in/2`, `update_in/2`, and other macros giving the same conveniences you would find in imperative languages while keeping the immutable properties of the language. From 5666e6c1d68802f9b9eb8b6153cd2129a8c9347e Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 21:46:14 +0200 Subject: [PATCH 04/15] Correct claim around module name needing to start with uppercase --- lib/elixir/pages/getting-started/modules-and-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index 25263746508..bf26b6849bf 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -12,7 +12,7 @@ iex> String.length("hello") 5 ``` -In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of the module must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): +In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of an elixir module must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): ```elixir iex> defmodule Math do From 8be7348c28b8c56da9ec02064549adebdcf5005e Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 21:50:26 +0200 Subject: [PATCH 05/15] Split out compilation and scripting docs --- .../compilation-and-scripting.md | 65 +++++++++++++++++++ .../getting-started/modules-and-functions.md | 61 +---------------- lib/elixir/scripts/elixir_docs.exs | 1 + 3 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 lib/elixir/pages/getting-started/compilation-and-scripting.md diff --git a/lib/elixir/pages/getting-started/compilation-and-scripting.md b/lib/elixir/pages/getting-started/compilation-and-scripting.md new file mode 100644 index 00000000000..c4c748f7958 --- /dev/null +++ b/lib/elixir/pages/getting-started/compilation-and-scripting.md @@ -0,0 +1,65 @@ + + +# Compilation and Scripting + +As our examples get longer in size, it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts. + +## Compilation + +Most of the time it is convenient to write [modules](modules-and-functions.md) (more closely looked at in the next chapter) into files so they can be compiled and reused. Let's assume we have a file named `math.ex` with the following contents: + +```elixir +defmodule Math do + def sum(a, b) do + a + b + end +end +``` + +This file can be compiled using `elixirc`: + +```console +$ elixirc math.ex +``` + +This will generate a file named `Elixir.Math.beam` containing the bytecode for the defined module. If we start `iex` again, our module definition will be available (provided that `iex` is started in the same directory the bytecode file is in): + +```elixir +iex> Math.sum(1, 2) +3 +``` + +## Scripting mode + +In addition to the Elixir file extension `.ex`, Elixir also supports `.exs` files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. This convention is followed by projects like `mix`. + +For instance, we can create a file called `math.exs`: + +```elixir +defmodule Math do + def sum(a, b) do + a + b + end +end + +IO.puts Math.sum(1, 2) +``` + +And execute it as: + +```console +$ elixir math.exs +``` + +Because we used `elixir` instead of `elixirc`, the module was compiled and loaded into memory, but no `.beam` file was written to disk. + +Elixir projects are usually organized into three directories: + +- `_build` - contains compilation artifacts +- `lib` - contains Elixir code (usually `.ex` files) +- `test` - contains tests (usually `.exs` files) + +When working on actual projects, the build tool called `mix` will be responsible for compiling and setting up the proper paths for you. For learning and convenience purposes, we recommend you to write the following code into script files and execute them as shown above. diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index bf26b6849bf..0c1a1e513b4 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -25,64 +25,7 @@ iex> Math.sum(1, 2) 3 ``` -In this chapter we will define our own modules, with different levels of complexity. As our examples get longer in size, it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts. - -## Compilation - -Most of the time it is convenient to write modules into files so they can be compiled and reused. Let's assume we have a file named `math.ex` with the following contents: - -```elixir -defmodule Math do - def sum(a, b) do - a + b - end -end -``` - -This file can be compiled using `elixirc`: - -```console -$ elixirc math.ex -``` - -This will generate a file named `Elixir.Math.beam` containing the bytecode for the defined module. If we start `iex` again, our module definition will be available (provided that `iex` is started in the same directory the bytecode file is in): - -```elixir -iex> Math.sum(1, 2) -3 -``` - -## Scripting mode - -In addition to the Elixir file extension `.ex`, Elixir also supports `.exs` files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. This convention is followed by projects like `mix`. - -For instance, we can create a file called `math.exs`: - -```elixir -defmodule Math do - def sum(a, b) do - a + b - end -end - -IO.puts Math.sum(1, 2) -``` - -And execute it as: - -```console -$ elixir math.exs -``` - -Because we used `elixir` instead of `elixirc`, the module was compiled and loaded into memory, but no `.beam` file was written to disk. - -Elixir projects are usually organized into three directories: - - * `_build` - contains compilation artifacts - * `lib` - contains Elixir code (usually `.ex` files) - * `test` - contains tests (usually `.exs` files) - -When working on actual projects, the build tool called `mix` will be responsible for compiling and setting up the proper paths for you. For learning and convenience purposes, we recommend you to write the following code into script files and execute them as shown above. +In this chapter we will define our own modules, with different levels of complexity. ## Function definition @@ -126,7 +69,7 @@ The trailing question mark in `zero?` means that this function returns a boolean Giving an argument that does not match any of the clauses raises an error. -Similar to constructs like `if`, function definitions support both `do:` and `do`-block syntax, as [we learned in the previous chapter](keywords-and-maps.md#do-blocks-and-keywords). For example, we can edit `math.exs` to look like this: +Similar to constructs like `if`, function definitions support both `do:` and `do`-block syntax, as [we learned in an earlier chapter](keywords-and-maps.md#do-blocks-and-keywords). For example, we can edit `math.exs` to look like this: ```elixir defmodule Math do diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index 74571ac0f8d..d4d5f2bf120 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -16,6 +16,7 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/getting-started/anonymous-functions.md", "lib/elixir/pages/getting-started/binaries-strings-and-charlists.md", "lib/elixir/pages/getting-started/keywords-and-maps.md", + "lib/elixir/pages/getting-started/compilation-and-scripting.md", "lib/elixir/pages/getting-started/modules-and-functions.md", "lib/elixir/pages/getting-started/recursion.md", "lib/elixir/pages/getting-started/enumerable-and-streams.md", From b821d35f90215c2eb9f4f04f5016b62cca3235f8 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 22:09:13 +0200 Subject: [PATCH 06/15] Move aliases and nesting to module guide --- .../alias-require-and-import.md | 79 +------------------ .../getting-started/modules-and-functions.md | 78 +++++++++++++++++- lib/elixir/scripts/elixir_docs.exs | 6 +- 3 files changed, 80 insertions(+), 83 deletions(-) diff --git a/lib/elixir/pages/getting-started/alias-require-and-import.md b/lib/elixir/pages/getting-started/alias-require-and-import.md index f77b78efae7..31d1564960e 100644 --- a/lib/elixir/pages/getting-started/alias-require-and-import.md +++ b/lib/elixir/pages/getting-started/alias-require-and-import.md @@ -156,83 +156,6 @@ end Since `use` allows any code to run, we can't really know the side-effects of using a module without reading its documentation. Therefore use this function with care and only if strictly required. Don't use `use` where an `import` or `alias` would do. -## Understanding Aliases - -At this point, you may be wondering: what exactly is an Elixir alias and how is it represented? - -An alias in Elixir is a capitalized identifier (like `String`, `Keyword`, etc) which is converted to an atom during compilation. For instance, the `String` alias translates by default to the atom `:"Elixir.String"`: - -```elixir -iex> is_atom(String) -true -iex> to_string(String) -"Elixir.String" -iex> :"Elixir.String" == String -true -``` - -By using the `alias/2` directive, we are changing the atom the alias expands to. - -Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms: - -```elixir -iex> List.flatten([1, [2], 3]) -[1, 2, 3] -iex> :"Elixir.List".flatten([1, [2], 3]) -[1, 2, 3] -``` - -That's the mechanism we use to call Erlang modules: - -```elixir -iex> :lists.flatten([1, [2], 3]) -[1, 2, 3] -``` - -## Module nesting - -Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example: - -```elixir -defmodule Foo do - defmodule Bar do - end -end -``` - -The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope. - -If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above. - -**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as: - -```elixir -defmodule Foo.Bar do -end - -defmodule Foo do - alias Foo.Bar - # Can still access it as `Bar` -end -``` - -Aliasing a nested module does not bring parent modules into scope. Consider the following example: - -```elixir -defmodule Foo do - defmodule Bar do - defmodule Baz do - end - end -end - -alias Foo.Bar.Baz -# The module `Foo.Bar.Baz` is now available as `Baz` -# However, the module `Foo.Bar` is *not* available as `Bar` -``` - -As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic. - ## Multi alias/import/require/use It is possible to `alias`, `import`, `require`, or `use` multiple modules at once. This is particularly useful once we start nesting modules, which is very common when building Elixir applications. For example, imagine you have an application where all modules are nested under `MyApp`, you can alias the modules `MyApp.Foo`, `MyApp.Bar` and `MyApp.Baz` at once as follows: @@ -241,4 +164,4 @@ It is possible to `alias`, `import`, `require`, or `use` multiple modules at onc alias MyApp.{Foo, Bar, Baz} ``` -With this, we have finished our tour of Elixir modules. The next topic to cover is module attributes. +With this, we have finished our tour of Elixir modules. diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index 0c1a1e513b4..b3342a3e4c9 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -12,7 +12,7 @@ iex> String.length("hello") 5 ``` -In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of an elixir module must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): +In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of an module name (an alias, as described further down) must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): ```elixir iex> defmodule Math do @@ -137,4 +137,78 @@ IO.puts(Concat.join("Hello", "world", "_")) #=> Hello_world When a variable is not used by a function or a clause, we add a leading underscore (`_`) to its name to signal this intent. This rule is also covered in our [Naming Conventions](../references/naming-conventions.md#underscore-_foo) document. -This finishes our short introduction to modules. In the next chapters, we will learn how to use function definitions for recursion and later on explore more functionality related to modules. +## Understanding Aliases + +An alias in Elixir is a capitalized identifier (like `String`, `Keyword`, etc) which is converted to an atom during compilation. For instance, the `String` alias translates by default to the atom `:"Elixir.String"`: + +```elixir +iex> is_atom(String) +true +iex> to_string(String) +"Elixir.String" +iex> :"Elixir.String" == String +true +``` + +By using the `alias/2` directive, we are changing the atom the alias expands to. + +Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms. By namespacing +those atoms elixir modules avoid conflicting with existing erlang modules. + +```elixir +iex> List.flatten([1, [2], 3]) +[1, 2, 3] +iex> :"Elixir.List".flatten([1, [2], 3]) +[1, 2, 3] +``` + +That's the mechanism we use to call Erlang modules: + +```elixir +iex> :lists.flatten([1, [2], 3]) +[1, 2, 3] +``` + +## Module nesting + +Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example: + +```elixir +defmodule Foo do + defmodule Bar do + end +end +``` + +The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope. + +If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above. + +**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as: + +```elixir +defmodule Foo.Bar do +end + +defmodule Foo do + alias Foo.Bar + # Can still access it as `Bar` +end +``` + +Aliasing a nested module does not bring parent modules into scope. Consider the following example: + +```elixir +defmodule Foo do + defmodule Bar do + defmodule Baz do + end + end +end + +alias Foo.Bar.Baz +# The module `Foo.Bar.Baz` is now available as `Baz` +# However, the module `Foo.Bar` is *not* available as `Bar` +``` + +As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic. diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index d4d5f2bf120..2842a93bd90 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -18,13 +18,13 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/getting-started/keywords-and-maps.md", "lib/elixir/pages/getting-started/compilation-and-scripting.md", "lib/elixir/pages/getting-started/modules-and-functions.md", + "lib/elixir/pages/getting-started/alias-require-and-import.md", + "lib/elixir/pages/getting-started/module-attributes.md", + "lib/elixir/pages/getting-started/structs.md", "lib/elixir/pages/getting-started/recursion.md", "lib/elixir/pages/getting-started/enumerable-and-streams.md", "lib/elixir/pages/getting-started/processes.md", "lib/elixir/pages/getting-started/io-and-the-file-system.md", - "lib/elixir/pages/getting-started/alias-require-and-import.md", - "lib/elixir/pages/getting-started/module-attributes.md", - "lib/elixir/pages/getting-started/structs.md", "lib/elixir/pages/getting-started/protocols.md", "lib/elixir/pages/getting-started/comprehensions.md", "lib/elixir/pages/getting-started/sigils.md", From d83564ace5d57db4407f7dfa5239d7578345e706 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Tue, 8 Jul 2025 22:12:46 +0200 Subject: [PATCH 07/15] Reorder --- lib/elixir/scripts/elixir_docs.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index 2842a93bd90..1511cb4ebf0 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -23,12 +23,12 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/getting-started/structs.md", "lib/elixir/pages/getting-started/recursion.md", "lib/elixir/pages/getting-started/enumerable-and-streams.md", - "lib/elixir/pages/getting-started/processes.md", - "lib/elixir/pages/getting-started/io-and-the-file-system.md", - "lib/elixir/pages/getting-started/protocols.md", "lib/elixir/pages/getting-started/comprehensions.md", + "lib/elixir/pages/getting-started/protocols.md", "lib/elixir/pages/getting-started/sigils.md", "lib/elixir/pages/getting-started/try-catch-and-rescue.md", + "lib/elixir/pages/getting-started/processes.md", + "lib/elixir/pages/getting-started/io-and-the-file-system.md", "lib/elixir/pages/getting-started/writing-documentation.md", "lib/elixir/pages/getting-started/optional-syntax.md", "lib/elixir/pages/getting-started/erlang-libraries.md", From fcaa986c449d713e59e9482ca5d60f277b5da6fd Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Wed, 9 Jul 2025 10:46:46 +0200 Subject: [PATCH 08/15] Add change in phrasing Co-authored-by: Zach Daniel --- lib/elixir/pages/getting-started/anonymous-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/anonymous-functions.md b/lib/elixir/pages/getting-started/anonymous-functions.md index b8160ca34b2..253ba3e650e 100644 --- a/lib/elixir/pages/getting-started/anonymous-functions.md +++ b/lib/elixir/pages/getting-started/anonymous-functions.md @@ -9,7 +9,7 @@ Anonymous functions allow us to store and pass executable code around as if it w ## Identifying functions and documentation -Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions – the functions defined on [modules](modules-and-functions.md). +Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions – the functions defined in [modules](modules-and-functions.md). Functions in Elixir are identified by both their name and their arity. The arity of a function describes the number of arguments that the function takes. From this point on we will use both the function name and its arity to describe functions throughout the documentation. `trunc/1` identifies the function which is named `trunc` and takes `1` argument, whereas `trunc/2` identifies a different (nonexistent) function with the same name but with an arity of `2`. From 06300deea0f002df3eb1bdfc7344fec31ba454e1 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Thu, 10 Jul 2025 08:59:43 +0200 Subject: [PATCH 09/15] Add section for elixir expressions --- .../pages/getting-started/case-cond-and-if.md | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index 2da7c568afd..76e7c922bcf 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -100,7 +100,22 @@ iex> if nil do "This will" ``` -This is also a good opportunity to talk about variable scoping in Elixir. If any variable is declared or changed inside [`if`](`if/2`), [`case`](`case/2`), and similar constructs, the declaration and change will only be visible inside the construct. For example: +> #### `if` is a macro {: .info} +> +> An interesting note regarding [`if`](`if/2`) is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information. + +If you find yourself nesting several [`if`](`if/2`) blocks, you may want to consider using [`cond`](`cond/1`) instead. Let's check it out. + +## Being expressions based + +This is also a good opportunity to talk about Elixir being an expressions based language and how that affects variable scoping. + +Being an expressions based language means that everything you use in the language is an expression, which +returns some value. This is contrary to other languages where there are language statements or even +functions "returning nothing", sometimes called `void`. + +This property of elixir allows variables to be scoped to individual blocks of code like inside [`if`](`if/2`), [`case`](`case/2`), where declarations or +changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example: ```elixir iex> x = 1 @@ -113,24 +128,24 @@ iex> x 1 ``` -In said cases, if you want to change a value, you must return the value from the [`if`](`if/2`): +You see the return value of the [`if`](`if/2`) expression as the resulting `2` here. To retain changes made within the [`if`](`if/2`) expression on the outer block you need to assign the returned value to a variable in the outer block. ```elixir iex> x = 1 1 -iex> x = if true do -...> x + 1 -...> else -...> x -...> end +iex> x = +...> if true do +...> x + 1 +...> else +...> x +...> end 2 ``` -> #### `if` is a macro {: .info} -> -> An interesting note regarding [`if`](`if/2`) is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information. - -If you find yourself nesting several [`if`](`if/2`) blocks, you may want to consider using [`cond`](`cond/1`) instead. Let's check it out. +With all expressions returning a value there's also no need for alternative syntaxes +like e.g. a ternary operator posing as an alternative to [`if`](`if/2`). You can +use [`if`](`if/2`) directly – as we'll [show later](keywords-and-maps.md#do-blocks-and-keywords) +there's even an inline notation for [`if`](`if/2`). ## cond From 2bfb5c99ac4fd46f731ae759ba6eb51b492f886c Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Thu, 10 Jul 2025 09:48:06 +0200 Subject: [PATCH 10/15] Revert "Split out compilation and scripting docs" This reverts commit 8be7348c28b8c56da9ec02064549adebdcf5005e. --- .../compilation-and-scripting.md | 65 ------------------- .../getting-started/modules-and-functions.md | 61 ++++++++++++++++- lib/elixir/scripts/elixir_docs.exs | 1 - 3 files changed, 59 insertions(+), 68 deletions(-) delete mode 100644 lib/elixir/pages/getting-started/compilation-and-scripting.md diff --git a/lib/elixir/pages/getting-started/compilation-and-scripting.md b/lib/elixir/pages/getting-started/compilation-and-scripting.md deleted file mode 100644 index c4c748f7958..00000000000 --- a/lib/elixir/pages/getting-started/compilation-and-scripting.md +++ /dev/null @@ -1,65 +0,0 @@ - - -# Compilation and Scripting - -As our examples get longer in size, it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts. - -## Compilation - -Most of the time it is convenient to write [modules](modules-and-functions.md) (more closely looked at in the next chapter) into files so they can be compiled and reused. Let's assume we have a file named `math.ex` with the following contents: - -```elixir -defmodule Math do - def sum(a, b) do - a + b - end -end -``` - -This file can be compiled using `elixirc`: - -```console -$ elixirc math.ex -``` - -This will generate a file named `Elixir.Math.beam` containing the bytecode for the defined module. If we start `iex` again, our module definition will be available (provided that `iex` is started in the same directory the bytecode file is in): - -```elixir -iex> Math.sum(1, 2) -3 -``` - -## Scripting mode - -In addition to the Elixir file extension `.ex`, Elixir also supports `.exs` files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. This convention is followed by projects like `mix`. - -For instance, we can create a file called `math.exs`: - -```elixir -defmodule Math do - def sum(a, b) do - a + b - end -end - -IO.puts Math.sum(1, 2) -``` - -And execute it as: - -```console -$ elixir math.exs -``` - -Because we used `elixir` instead of `elixirc`, the module was compiled and loaded into memory, but no `.beam` file was written to disk. - -Elixir projects are usually organized into three directories: - -- `_build` - contains compilation artifacts -- `lib` - contains Elixir code (usually `.ex` files) -- `test` - contains tests (usually `.exs` files) - -When working on actual projects, the build tool called `mix` will be responsible for compiling and setting up the proper paths for you. For learning and convenience purposes, we recommend you to write the following code into script files and execute them as shown above. diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index b3342a3e4c9..7f8bd85adf0 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -25,7 +25,64 @@ iex> Math.sum(1, 2) 3 ``` -In this chapter we will define our own modules, with different levels of complexity. +In this chapter we will define our own modules, with different levels of complexity. As our examples get longer in size, it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts. + +## Compilation + +Most of the time it is convenient to write modules into files so they can be compiled and reused. Let's assume we have a file named `math.ex` with the following contents: + +```elixir +defmodule Math do + def sum(a, b) do + a + b + end +end +``` + +This file can be compiled using `elixirc`: + +```console +$ elixirc math.ex +``` + +This will generate a file named `Elixir.Math.beam` containing the bytecode for the defined module. If we start `iex` again, our module definition will be available (provided that `iex` is started in the same directory the bytecode file is in): + +```elixir +iex> Math.sum(1, 2) +3 +``` + +## Scripting mode + +In addition to the Elixir file extension `.ex`, Elixir also supports `.exs` files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. This convention is followed by projects like `mix`. + +For instance, we can create a file called `math.exs`: + +```elixir +defmodule Math do + def sum(a, b) do + a + b + end +end + +IO.puts Math.sum(1, 2) +``` + +And execute it as: + +```console +$ elixir math.exs +``` + +Because we used `elixir` instead of `elixirc`, the module was compiled and loaded into memory, but no `.beam` file was written to disk. + +Elixir projects are usually organized into three directories: + + * `_build` - contains compilation artifacts + * `lib` - contains Elixir code (usually `.ex` files) + * `test` - contains tests (usually `.exs` files) + +When working on actual projects, the build tool called `mix` will be responsible for compiling and setting up the proper paths for you. For learning and convenience purposes, we recommend you to write the following code into script files and execute them as shown above. ## Function definition @@ -69,7 +126,7 @@ The trailing question mark in `zero?` means that this function returns a boolean Giving an argument that does not match any of the clauses raises an error. -Similar to constructs like `if`, function definitions support both `do:` and `do`-block syntax, as [we learned in an earlier chapter](keywords-and-maps.md#do-blocks-and-keywords). For example, we can edit `math.exs` to look like this: +Similar to constructs like `if`, function definitions support both `do:` and `do`-block syntax, as [we learned in the previous chapter](keywords-and-maps.md#do-blocks-and-keywords). For example, we can edit `math.exs` to look like this: ```elixir defmodule Math do diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index 1511cb4ebf0..36478cc00a6 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -16,7 +16,6 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/getting-started/anonymous-functions.md", "lib/elixir/pages/getting-started/binaries-strings-and-charlists.md", "lib/elixir/pages/getting-started/keywords-and-maps.md", - "lib/elixir/pages/getting-started/compilation-and-scripting.md", "lib/elixir/pages/getting-started/modules-and-functions.md", "lib/elixir/pages/getting-started/alias-require-and-import.md", "lib/elixir/pages/getting-started/module-attributes.md", From b26b2156a46d06361d9378e19d181fa493f3a506 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Fri, 11 Jul 2025 19:54:10 +0200 Subject: [PATCH 11/15] Update lib/elixir/pages/getting-started/keywords-and-maps.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/elixir/pages/getting-started/keywords-and-maps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/keywords-and-maps.md b/lib/elixir/pages/getting-started/keywords-and-maps.md index 26889f634a2..656e84c96ca 100644 --- a/lib/elixir/pages/getting-started/keywords-and-maps.md +++ b/lib/elixir/pages/getting-started/keywords-and-maps.md @@ -225,7 +225,7 @@ These operations have one large benefit in that they raise if the key does not e Elixir developers typically prefer to use the `map.key` syntax and pattern matching instead of the functions in the `Map` module when working with maps because they lead to an assertive style of programming. [This blog post by José Valim](https://dashbit.co/blog/writing-assertive-code-with-elixir) provides insight and examples on how you get more concise and faster software by writing assertive code in Elixir. -In a further chapter you'll learn about ["Structs"](structs.md) in elixir, which further enforce the idea of a map with predefined keys. +In a further chapter you'll learn about ["Structs"](structs.md), which further enforce the idea of a map with predefined keys. ## Nested data structures From 2d5c56d7342b8624899188cd2d4835e709784acc Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Fri, 11 Jul 2025 19:54:56 +0200 Subject: [PATCH 12/15] Update lib/elixir/pages/getting-started/case-cond-and-if.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/elixir/pages/getting-started/case-cond-and-if.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index 76e7c922bcf..e6f55876e45 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -142,10 +142,7 @@ iex> x = 2 ``` -With all expressions returning a value there's also no need for alternative syntaxes -like e.g. a ternary operator posing as an alternative to [`if`](`if/2`). You can -use [`if`](`if/2`) directly – as we'll [show later](keywords-and-maps.md#do-blocks-and-keywords) -there's even an inline notation for [`if`](`if/2`). +With all expressions returning a value there's also no need for alternative constructs, such as ternary operators posing as an alternative to [`if`](`if/2`). Elixir does include an inline notation for [`if`](`if/2`) and, as we will [learn later](keywords-and-maps.md#do-blocks-and-keywords), it is a syntactic variation on `if`'s arguments. ## cond From 45707d458a7ad4c511e4f46b825c3e605ed458a0 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Fri, 11 Jul 2025 19:55:31 +0200 Subject: [PATCH 13/15] Update lib/elixir/pages/getting-started/case-cond-and-if.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/elixir/pages/getting-started/case-cond-and-if.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index e6f55876e45..df5c55d3b09 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -106,16 +106,11 @@ iex> if nil do If you find yourself nesting several [`if`](`if/2`) blocks, you may want to consider using [`cond`](`cond/1`) instead. Let's check it out. -## Being expressions based +## Expressions -This is also a good opportunity to talk about Elixir being an expressions based language and how that affects variable scoping. +Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value. -Being an expressions based language means that everything you use in the language is an expression, which -returns some value. This is contrary to other languages where there are language statements or even -functions "returning nothing", sometimes called `void`. - -This property of elixir allows variables to be scoped to individual blocks of code like inside [`if`](`if/2`), [`case`](`case/2`), where declarations or -changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example: +This property allows variables to be scoped to individual blocks of code such as [`if`](`if/2`), [`case`](`case/2`), where declarations or changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example: ```elixir iex> x = 1 From 2922ca8d43453d953cea47fcb66e3797b0db8b80 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Fri, 11 Jul 2025 19:58:48 +0200 Subject: [PATCH 14/15] Move macro and hook to cond back --- .../pages/getting-started/case-cond-and-if.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index df5c55d3b09..ebb3bfed327 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -100,15 +100,9 @@ iex> if nil do "This will" ``` -> #### `if` is a macro {: .info} -> -> An interesting note regarding [`if`](`if/2`) is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information. - -If you find yourself nesting several [`if`](`if/2`) blocks, you may want to consider using [`cond`](`cond/1`) instead. Let's check it out. - ## Expressions -Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value. +Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value. This property allows variables to be scoped to individual blocks of code such as [`if`](`if/2`), [`case`](`case/2`), where declarations or changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example: @@ -139,6 +133,12 @@ iex> x = With all expressions returning a value there's also no need for alternative constructs, such as ternary operators posing as an alternative to [`if`](`if/2`). Elixir does include an inline notation for [`if`](`if/2`) and, as we will [learn later](keywords-and-maps.md#do-blocks-and-keywords), it is a syntactic variation on `if`'s arguments. +> #### `if` is a macro {: .info} +> +> An interesting note regarding [`if`](`if/2`) is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information. + +If you find yourself nesting several [`if`](`if/2`) blocks, you may want to consider using [`cond`](`cond/1`) instead. Let's check it out. + ## cond We have used `case` to find a matching clause from many patterns. We have used `if` to check for a single condition. If you need to check across several conditions and find the first one that does not evaluate to `nil` or `false`, [`cond`](`cond/1`) is a useful construct: From c2c10a86d1d180141ec14c7a225ae7720afabd06 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Fri, 11 Jul 2025 20:10:19 +0200 Subject: [PATCH 15/15] Drop expression headline level --- lib/elixir/pages/getting-started/case-cond-and-if.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index ebb3bfed327..9c354105e5c 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -100,7 +100,7 @@ iex> if nil do "This will" ``` -## Expressions +### Expressions Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value.