diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..944880fa1 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.0 diff --git a/Gemfile b/Gemfile index b3b1956fc..f24d05f4a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,7 @@ source 'https://rubygems.org' gem 'github-pages' gem 'json', '>= 2.0.0' - -gem "webrick", "~> 1.8" +gem 'webrick', '>= 1.8' +gem 'csv', '~> 3.3' +gem 'base64', '~> 0.2.0' +gem 'bigdecimal', '~> 3.1' diff --git a/Gemfile.lock b/Gemfile.lock index 2e71f3824..f77a0774e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,20 +1,23 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.0.4.3) + activesupport (7.0.7.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.9) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.23.9) + commonmarker (0.23.10) concurrent-ruby (1.2.2) + csv (3.3.3) dnsruby (1.61.9) simpleidn (~> 0.1) em-websocket (0.5.3) @@ -86,7 +89,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) jekyll (3.9.3) addressable (~> 2.4) @@ -206,14 +209,14 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.8.1) + mini_portile2 (2.8.9) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.18.0) - nokogiri (1.14.3) - mini_portile2 (~> 2.8.0) + minitest (5.19.0) + nokogiri (1.18.9) + mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) @@ -221,11 +224,11 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) - racc (1.6.2) + racc (1.8.1) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.4.2) rouge (3.26.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -250,15 +253,18 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.8.1) + webrick (1.8.2) PLATFORMS ruby DEPENDENCIES + base64 (~> 0.2.0) + bigdecimal (~> 3.1) + csv (~> 3.3) github-pages json (>= 2.0.0) - webrick (~> 1.8) + webrick (>= 1.8) BUNDLED WITH - 2.3.7 + 2.5.23 diff --git a/_config.yml b/_config.yml index 4481c167a..635db0b94 100644 --- a/_config.yml +++ b/_config.yml @@ -26,3 +26,4 @@ defaults: values: layout: post permalink: /blog/:year/:month/:day/:title/ + image: /images/social/elixir-og-card.jpg diff --git a/_data/elixir-versions.yml b/_data/elixir-versions.yml index e5be50ba9..468bdabbf 100644 --- a/_data/elixir-versions.yml +++ b/_data/elixir-versions.yml @@ -1,10 +1,37 @@ -stable: v1_15 +stable: v1_19 + +v1_19: + name: v1.19 + minimum_otp: 26.0 + recommended_otp: 28.1 + otp_versions: [28, 27, 26] + version: 1.19.4 + +v1_18: + name: v1.18 + minimum_otp: 26.0 + recommended_otp: 27.3.4 + otp_versions: [27, 26, 25] + version: 1.18.4 + +v1_17: + name: v1.17 + minimum_otp: 25.0 + recommended_otp: 27.1.2 + otp_versions: [27, 26, 25] + version: 1.17.3 + +v1_16: + name: v1.16 + minimum_otp: 24.0 + otp_versions: [26, 25, 24] + version: 1.16.3 v1_15: name: v1.15 minimum_otp: 24.0 otp_versions: [26, 25, 24] - version: 1.15.4 + version: 1.15.8 v1_14: name: v1.14 diff --git a/_data/getting-started.yml b/_data/getting-started.yml deleted file mode 100644 index 515e578b9..000000000 --- a/_data/getting-started.yml +++ /dev/null @@ -1,123 +0,0 @@ -- title: Getting Started - dir: /getting-started/ - pages: - - title: Introduction - slug: introduction - - - title: Basic types - slug: basic-types - - - title: Basic operators - slug: basic-operators - - - title: Pattern matching - slug: pattern-matching - - - title: case, cond, and if - slug: case-cond-and-if - - - title: Binaries, strings, and charlists - slug: binaries-strings-and-char-lists - - - title: Keyword lists and maps - slug: keywords-and-maps - - - title: Modules and Functions - slug: modules-and-functions - - - title: Recursion - slug: recursion - - - title: Enumerables and streams - slug: enumerables-and-streams - - - title: Processes - slug: processes - - - title: IO and the file system - slug: io-and-the-file-system - - - title: alias, require, and import - slug: alias-require-and-import - - - title: Module attributes - slug: module-attributes - - - title: Structs - slug: structs - - - title: Protocols - slug: protocols - - - title: Comprehensions - slug: comprehensions - - - title: Sigils - slug: sigils - - - title: try, catch, and rescue - slug: try-catch-and-rescue - - - title: Optional syntax sheet - slug: optional-syntax - - - title: Erlang libraries - slug: erlang-libraries - - - title: Debugging - slug: debugging - - - title: Typespecs and behaviours - slug: typespecs-and-behaviours - - - title: Where to go next - slug: where-to-go-next - -- title: Mix and OTP - dir: /getting-started/mix-otp/ - pages: - - title: Introduction to Mix - slug: introduction-to-mix - - - title: Agent - slug: agent - - - title: GenServer - slug: genserver - - - title: Supervisor and Application - slug: supervisor-and-application - - - title: Dynamic supervisors - slug: dynamic-supervisor - - - title: ETS - slug: ets - - - title: Dependencies and umbrella projects - slug: dependencies-and-umbrella-projects - - - title: Task and gen_tcp - slug: task-and-gen-tcp - - - title: Doctests, patterns and with - slug: docs-tests-and-with - - - title: Distributed tasks and tags - slug: distributed-tasks - - - title: Configuration and releases - slug: config-and-releases - - -- title: Meta-programming in Elixir - dir: /getting-started/meta/ - pages: - - title: Quote and unquote - slug: quote-and-unquote - - - title: Macros - slug: macros - - - title: Domain-specific languages - slug: domain-specific-languages diff --git a/_includes/events.html b/_includes/events.html index e6845ed63..e8ed467c5 100644 --- a/_includes/events.html +++ b/_includes/events.html @@ -1,11 +1,4 @@ -
+Click on the cases below to learn more about how companies across different industries are using the power of Elixir and its ecosystem to create and grow their businesses. Cases are listed in the order they have been published.
-
-## Observer
-
-For debugging complex systems, jumping at the code is not enough. It is necessary to have an understanding of the whole virtual machine, processes, applications, as well as set up tracing mechanisms. Luckily this can be achieved in Erlang with `:observer`. In your application:
-
-```elixir
-$ iex
-iex> :observer.start()
-```
-
-> Similar to the `debugger` note above, your package manager may require a separate installation in order to run Observer.
-
-The above will open another Graphical User Interface that provides many panes to fully understand and navigate the runtime and your project:
-
-
-
-We explore the Observer in the context of an actual project [in the Dynamic Supervisor chapter of the Mix & OTP guide](/getting-started/mix-otp/dynamic-supervisor.html). This is one of the debugging techniques [the Phoenix framework used to achieve 2 million connections on a single machine](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections).
-
-If you are using the Phoenix web framework, it ships with the [Phoenix LiveDashboard](https://github.com/phoenixframework/phoenix_live_dashboard), a web dashboard for production nodes which provides similar features to Observer.
-
-Finally, remember you can also get a mini-overview of the runtime info by calling `runtime_info/0` directly in IEx.
-
-## Other tools and community
-
-We have just scratched the surface of what the Erlang VM has to offer, for example:
-
- * Alongside the observer application, Erlang also includes a `:crashdump_viewer` to view crash dumps
-
- * Integration with OS level tracers, such as [Linux Trace Toolkit,](http://www.erlang.org/doc/apps/runtime_tools/LTTng.html) [DTRACE,](http://www.erlang.org/doc/apps/runtime_tools/DTRACE.html) and [SystemTap](http://www.erlang.org/doc/apps/runtime_tools/SYSTEMTAP.html)
-
- * [Microstate accounting](http://www.erlang.org/doc/man/msacc.html) measures how much time the runtime spends in several low-level tasks in a short time interval
-
- * Mix ships with many tasks under the `profile` namespace, such as `cprof` and `fprof`
-
- * For more advanced use cases, we recommend the excellent [Erlang in Anger](https://www.erlang-in-anger.com/), which is available as a free ebook
-
-Happy debugging!
diff --git a/getting-started/enumerables-and-streams.markdown b/getting-started/enumerables-and-streams.markdown
index 7e1b18d19..b6abedae7 100644
--- a/getting-started/enumerables-and-streams.markdown
+++ b/getting-started/enumerables-and-streams.markdown
@@ -1,126 +1,5 @@
---
-section: getting-started
-layout: getting-started
-title: Enumerables and Streams
+layout: redirect
+sitemap: false
+redirect_to: enumerable-and-streams
---
-
-## Enumerables
-
-Elixir provides the concept of enumerables and [the `Enum` module](https://hexdocs.pm/elixir/Enum.html) to work with them. We have already learned two enumerables: lists and maps.
-
-```elixir
-iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
-[2, 4, 6]
-iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
-[2, 12]
-```
-
-The `Enum` module provides a huge range of functions to transform, sort, group, filter and retrieve items from enumerables. It is one of the modules developers use frequently in their Elixir code.
-
-Elixir also provides ranges:
-
-```elixir
-iex> Enum.map(1..3, fn x -> x * 2 end)
-[2, 4, 6]
-iex> Enum.reduce(1..3, 0, &+/2)
-6
-```
-
-The functions in the Enum module are limited to, as the name says, enumerating values in data structures. For specific operations, like inserting and updating particular elements, you may need to reach for modules specific to the data type. For example, if you want to insert an element at a given position in a list, you should use the `List.insert_at/3` function from [the `List` module](https://hexdocs.pm/elixir/List.html), as it would make little sense to insert a value into, for example, a range.
-
-We say the functions in the `Enum` module are polymorphic because they can work with diverse data types. In particular, the functions in the `Enum` module can work with any data type that implements [the `Enumerable` protocol](https://hexdocs.pm/elixir/Enumerable.html). We are going to discuss Protocols in a later chapter; for now we are going to move on to a specific kind of enumerable called a stream.
-
-## Eager vs Lazy
-
-All the functions in the `Enum` module are eager. Many functions expect an enumerable and return a list back:
-
-```elixir
-iex> odd? = &(rem(&1, 2) != 0)
-#Function<6.80484245/1 in :erl_eval.expr/5>
-iex> Enum.filter(1..3, odd?)
-[1, 3]
-```
-
-This means that when performing multiple operations with `Enum`, each operation is going to generate an intermediate list until we reach the result:
-
-```elixir
-iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum()
-7500000000
-```
-
-The example above has a pipeline of operations. We start with a range and then multiply each element in the range by 3. This first operation will now create and return a list with `100_000` items. Then we keep all odd elements from the list, generating a new list, now with `50_000` items, and then we sum all entries.
-
-## The pipe operator
-
-The `|>` symbol used in the snippet above is the **pipe operator**: it takes the output from the expression on its left side and passes it as the first argument to the function call on its right side. It's similar to the Unix `|` operator. Its purpose is to highlight the data being transformed by a series of functions. To see how it can make the code cleaner, have a look at the example above rewritten without using the `|>` operator:
-
-```elixir
-iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
-7500000000
-```
-
-Find more about the pipe operator [by reading its documentation](https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2).
-
-## Streams
-
-As an alternative to `Enum`, Elixir provides [the `Stream` module](https://hexdocs.pm/elixir/Stream.html) which supports lazy operations:
-
-```elixir
-iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
-7500000000
-```
-
-Streams are lazy, composable enumerables.
-
-In the example above, `1..100_000 |> Stream.map(&(&1 * 3))` returns a data type, an actual stream, that represents the `map` computation over the range `1..100_000`:
-
-```elixir
-iex> 1..100_000 |> Stream.map(&(&1 * 3))
-#Stream<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>
-```
-
-Furthermore, they are composable because we can pipe many stream operations:
-
-```elixir
-iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)
-#Stream<[enum: 1..100000, funs: [...]]>
-```
-
-Instead of generating intermediate lists, streams build a series of computations that are invoked only when we pass the underlying stream to the `Enum` module. Streams are useful when working with large, *possibly infinite*, collections.
-
-Many functions in the `Stream` module accept any enumerable as an argument and return a stream as a result. It also provides functions for creating streams. For example, `Stream.cycle/1` can be used to create a stream that cycles a given enumerable infinitely. Be careful to not call a function like `Enum.map/2` on such streams, as they would cycle forever:
-
-```elixir
-iex> stream = Stream.cycle([1, 2, 3])
-#Function<15.16982430/2 in Stream.unfold/2>
-iex> Enum.take(stream, 10)
-[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
-```
-
-On the other hand, `Stream.unfold/2` can be used to generate values from a given initial value:
-
-```elixir
-iex> stream = Stream.unfold("hełło", &String.next_codepoint/1)
-#Function<39.75994740/2 in Stream.unfold/2>
-iex> Enum.take(stream, 3)
-["h", "e", "ł"]
-```
-
-Another interesting function is `Stream.resource/3` which can be used to wrap around resources, guaranteeing they are opened right before enumeration and closed afterwards, even in the case of failures. For example, `File.stream!/1` builds on top of `Stream.resource/3` to stream files:
-
-```elixir
-iex> stream = File.stream!("path/to/file")
-%File.Stream{
- line_or_bytes: :line,
- modes: [:raw, :read_ahead, :binary],
- path: "path/to/file",
- raw: true
-}
-iex> Enum.take(stream, 10)
-```
-
-The example above will fetch the first 10 lines of the file you have selected. This means streams can be very useful for handling large files or even slow resources like network resources.
-
-The amount of functionality in the [`Enum`](https://hexdocs.pm/elixir/Enum.html) and [`Stream`](https://hexdocs.pm/elixir/Stream.html) modules can be daunting at first, but you will get familiar with them case by case. In particular, focus on the `Enum` module first and only move to `Stream` for the particular scenarios where laziness is required, to either deal with slow resources or large, possibly infinite, collections.
-
-Next, we'll look at a feature central to Elixir, Processes, which allows us to write concurrent, parallel and distributed programs in an easy and understandable way.
diff --git a/getting-started/erlang-libraries.markdown b/getting-started/erlang-libraries.markdown
index 5719d9175..8af923cc7 100644
--- a/getting-started/erlang-libraries.markdown
+++ b/getting-started/erlang-libraries.markdown
@@ -1,208 +1,5 @@
---
-section: getting-started
-layout: getting-started
-title: Erlang libraries
+layout: redirect
+sitemap: false
+redirect_to: erlang-libraries
---
-
-Elixir provides excellent interoperability with Erlang libraries. In fact,
-Elixir discourages simply wrapping Erlang libraries in favor of directly
-interfacing with Erlang code. In this section, we will present some of the
-most common and useful Erlang functionality that is not found in Elixir.
-
-As you grow more proficient in Elixir, you may want to explore the Erlang
-[STDLIB Reference Manual](http://www.erlang.org/doc/apps/stdlib/index.html) in more
-detail.
-
-## The binary module
-
-The built-in Elixir String module handles binaries that are UTF-8 encoded.
-[The binary module](http://www.erlang.org/doc/man/binary.html) is useful when
-you are dealing with binary data that is not necessarily UTF-8 encoded.
-
-```elixir
-iex> String.to_charlist "Ø"
-[216]
-iex> :binary.bin_to_list "Ø"
-[195, 152]
-```
-
-The above example shows the difference; the `String` module returns Unicode
-codepoints, while `:binary` deals with raw data bytes.
-
-## Formatted text output
-
-Elixir does not contain a function similar to `printf` found in C and other
-languages. Luckily, the Erlang standard library functions `:io.format/2` and
-`:io_lib.format/2` may be used. The first formats to terminal output, while
-the second formats to an iolist. The format specifiers differ from `printf`,
-[refer to the Erlang documentation for details](http://www.erlang.org/doc/man/io.html#format-1).
-
-```elixir
-iex> :io.format("Pi is approximately given by:~10.3f~n", [:math.pi])
-Pi is approximately given by: 3.142
-:ok
-iex> to_string :io_lib.format("Pi is approximately given by:~10.3f~n", [:math.pi])
-"Pi is approximately given by: 3.142\n"
-```
-
-Also note that Erlang's formatting functions require special attention to
-Unicode handling.
-
-## The crypto module
-
-[The crypto module](http://www.erlang.org/doc/man/crypto.html) contains hashing
-functions, digital signatures, encryption and more:
-
-```elixir
-iex> Base.encode16(:crypto.hash(:sha256, "Elixir"))
-"3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB"
-```
-
-The `:crypto` module is part of the `:crypto` application that ships with
-Erlang. This means you must list the `:crypto` application as an additional
-application in your project configuration. To do this, edit your `mix.exs`
-file to include:
-
-```elixir
-def application do
- [extra_applications: [:crypto]]
-end
-```
-
-Any module that is not part of the `:kernel` or `:stdlib` Erlang applications
-must have their application explicitly listed in your `mix.exs`. You can find
-the application name of any Erlang module in the Erlang documentation, immediately
-below the Erlang logo in the sidebar.
-
-## The digraph module
-
-[The digraph module](http://www.erlang.org/doc/man/digraph.html) (as well as
-[digraph_utils](http://www.erlang.org/doc/man/digraph_utils.html)) contains
-functions for dealing with directed graphs built of vertices and edges.
-After constructing the graph, the algorithms in there will help find,
-for instance, the shortest path between two vertices, or loops in the graph.
-
-Given three vertices, find the shortest path from the first to the last.
-
-```elixir
-iex> digraph = :digraph.new()
-iex> coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
-iex> [v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
-iex> :digraph.add_edge(digraph, v0, v1)
-iex> :digraph.add_edge(digraph, v1, v2)
-iex> :digraph.get_short_path(digraph, v0, v2)
-[{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
-```
-
-Note that the functions in `:digraph` alter the graph structure in-place, this
-is possible because they are implemented as ETS tables, explained next.
-
-## Erlang Term Storage
-
-The modules [`ets`](http://www.erlang.org/doc/man/ets.html) and
-[`dets`](http://www.erlang.org/doc/man/dets.html) handle storage of large
-data structures in memory or on disk respectively.
-
-ETS lets you create a table containing tuples. By default, ETS tables
-are protected, which means only the owner process may write to the table
-but any other process can read. ETS has some functionality to allow a
-table to be used as a simple database, a key-value store or as a cache
-mechanism.
-
-The functions in the `ets` module will modify the state of the table as a
-side-effect.
-
-```elixir
-iex> table = :ets.new(:ets_test, [])
-# Store as tuples with {name, population}
-iex> :ets.insert(table, {"China", 1_374_000_000})
-iex> :ets.insert(table, {"India", 1_284_000_000})
-iex> :ets.insert(table, {"USA", 322_000_000})
-iex> :ets.i(table)
-<1 > {<<"India">>,1284000000}
-<2 > {<<"USA">>,322000000}
-<3 > {<<"China">>,1374000000}
-```
-
-## The math module
-
-[The `math` module](http://www.erlang.org/doc/man/math.html) contains common
-mathematical operations covering trigonometry, exponential, and logarithmic
-functions.
-
-```elixir
-iex> angle_45_deg = :math.pi() * 45.0 / 180.0
-iex> :math.sin(angle_45_deg)
-0.7071067811865475
-iex> :math.exp(55.0)
-7.694785265142018e23
-iex> :math.log(7.694785265142018e23)
-55.0
-```
-
-## The queue module
-
-The [`queue` is a data structure](http://www.erlang.org/doc/man/queue.html)
-that implements (double-ended) FIFO (first-in first-out) queues efficiently:
-
-```elixir
-iex> q = :queue.new
-iex> q = :queue.in("A", q)
-iex> q = :queue.in("B", q)
-iex> {value, q} = :queue.out(q)
-iex> value
-{:value, "A"}
-iex> {value, q} = :queue.out(q)
-iex> value
-{:value, "B"}
-iex> {value, q} = :queue.out(q)
-iex> value
-:empty
-```
-
-## The rand module
-
-[`rand` has functions](http://www.erlang.org/doc/man/rand.html) for returning
-random values and setting the random seed.
-
-```elixir
-iex> :rand.uniform()
-0.8175669086010815
-iex> _ = :rand.seed(:exs1024, {123, 123534, 345345})
-iex> :rand.uniform()
-0.5820506340260994
-iex> :rand.uniform(6)
-6
-```
-
-## The zip and zlib modules
-
-[The `zip` module](http://www.erlang.org/doc/man/zip.html) lets you read and write
-ZIP files to and from disk or memory, as well as extracting file information.
-
-This code counts the number of files in a ZIP file:
-
-```elixir
-iex> :zip.foldl(fn _, _, _, acc -> acc + 1 end, 0, :binary.bin_to_list("file.zip"))
-{:ok, 633}
-```
-
-[The `zlib` module](http://www.erlang.org/doc/man/zlib.html) deals with data compression in zlib format, as found in the
-`gzip` command.
-
-```elixir
-iex> song = "
-...> Mary had a little lamb,
-...> His fleece was white as snow,
-...> And everywhere that Mary went,
-...> The lamb was sure to go."
-iex> compressed = :zlib.compress(song)
-iex> byte_size song
-110
-iex> byte_size compressed
-99
-iex> :zlib.uncompress(compressed)
-"\nMary had a little lamb,\nHis fleece was white as snow,\nAnd everywhere that Mary went,\nThe lamb was sure to go."
-```
-
-Now let's take a look at existing Elixir (and Erlang) libraries you might use while debugging.
\ No newline at end of file
diff --git a/getting-started/index.html b/getting-started/index.html
index a5a74f690..c50365d75 100644
--- a/getting-started/index.html
+++ b/getting-started/index.html
@@ -1,7 +1,5 @@
-
-
-
-
-
-Not only that, as you create new buckets on the terminal, you should see new processes spawned in the supervision tree shown in Observer:
-
-```elixir
-iex> KV.Registry.create(KV.Registry, "shopping")
-:ok
-```
-
-We will leave it up to you to further explore what Observer provides. Note you can double click any process in the supervision tree to retrieve more information about it, as well as right-click a process to send "a kill signal", a perfect way to emulate failures and see if your supervisor reacts as expected.
-
-At the end of the day, tools like Observer are one of the reasons you want to always start processes inside supervision trees, even if they are temporary, to ensure they are always reachable and introspectable.
-
-Now that our buckets are properly linked and supervised, let's see how we can speed things up.
diff --git a/getting-started/mix-otp/ets.markdown b/getting-started/mix-otp/ets.markdown
index 06cabbe22..22e0a0978 100644
--- a/getting-started/mix-otp/ets.markdown
+++ b/getting-started/mix-otp/ets.markdown
@@ -1,290 +1,5 @@
---
-section: getting-started
-layout: getting-started
-title: ETS
-category: Mix and OTP
+layout: redirect
+sitemap: false
+redirect_to: erlang-term-storage
---
-
-{% include mix-otp-preface.html %}
-
-Every time we need to look up a bucket, we need to send a message to the registry. In case our registry is being accessed concurrently by multiple processes, the registry may become a bottleneck!
-
-In this chapter, we will learn about ETS (Erlang Term Storage) and how to use it as a cache mechanism.
-
-> Warning! Don't use ETS as a cache prematurely! Log and analyze your application performance and identify which parts are bottlenecks, so you know *whether* you should cache, and *what* you should cache. This chapter is merely an example of how ETS can be used, once you've determined the need.
-
-## ETS as a cache
-
-ETS allows us to store any Elixir term in an in-memory table. Working with ETS tables is done via [Erlang's `:ets` module](http://www.erlang.org/doc/man/ets.html):
-
-```elixir
-iex> table = :ets.new(:buckets_registry, [:set, :protected])
-#Reference<0.1885502827.460455937.234656>
-iex> :ets.insert(table, {"foo", self()})
-true
-iex> :ets.lookup(table, "foo")
-[{"foo", #PID<0.41.0>}]
-```
-
-When creating an ETS table, two arguments are required: the table name and a set of options. From the available options, we passed the table type and its access rules. We have chosen the `:set` type, which means that keys cannot be duplicated. We've also set the table's access to `:protected`, meaning only the process that created the table can write to it, but all processes can read from it. The possible access controls:
-
- `:public` — Read/Write available to all processes.
-
- `:protected` — Read available to all processes. Only writable by owner process. This is the default.
-
- `:private` — Read/Write limited to owner process.
-
-Be aware that if your Read/Write call violates the access control, the operation will raise `ArgumentError`. Finally, since `:set` and `:protected` are the default values, we will skip them from now on.
-
-ETS tables can also be named, allowing us to access them by a given name:
-
-```elixir
-iex> :ets.new(:buckets_registry, [:named_table])
-:buckets_registry
-iex> :ets.insert(:buckets_registry, {"foo", self()})
-true
-iex> :ets.lookup(:buckets_registry, "foo")
-[{"foo", #PID<0.41.0>}]
-```
-
-Let's change the `KV.Registry` to use ETS tables. The first change is to modify our registry to require a name argument, we will use it to name the ETS table and the registry process itself. ETS names and process names are stored in different locations, so there is no chance of conflicts.
-
-Open up `lib/kv/registry.ex`, and let's change its implementation. We've added comments to the source code to highlight the changes we've made:
-
-```elixir
-defmodule KV.Registry do
- use GenServer
-
- ## Client API
-
- @doc """
- Starts the registry with the given options.
-
- `:name` is always required.
- """
- def start_link(opts) do
- # 1. Pass the name to GenServer's init
- server = Keyword.fetch!(opts, :name)
- GenServer.start_link(__MODULE__, server, opts)
- end
-
- @doc """
- Looks up the bucket pid for `name` stored in `server`.
-
- Returns `{:ok, pid}` if the bucket exists, `:error` otherwise.
- """
- def lookup(server, name) do
- # 2. Lookup is now done directly in ETS, without accessing the server
- case :ets.lookup(server, name) do
- [{^name, pid}] -> {:ok, pid}
- [] -> :error
- end
- end
-
- @doc """
- Ensures there is a bucket associated with the given `name` in `server`.
- """
- def create(server, name) do
- GenServer.cast(server, {:create, name})
- end
-
- ## Server callbacks
-
- @impl true
- def init(table) do
- # 3. We have replaced the names map by the ETS table
- names = :ets.new(table, [:named_table, read_concurrency: true])
- refs = %{}
- {:ok, {names, refs}}
- end
-
- # 4. The previous handle_call callback for lookup was removed
-
- @impl true
- def handle_cast({:create, name}, {names, refs}) do
- # 5. Read and write to the ETS table instead of the map
- case lookup(names, name) do
- {:ok, _pid} ->
- {:noreply, {names, refs}}
- :error ->
- {:ok, pid} = DynamicSupervisor.start_child(KV.BucketSupervisor, KV.Bucket)
- ref = Process.monitor(pid)
- refs = Map.put(refs, ref, name)
- :ets.insert(names, {name, pid})
- {:noreply, {names, refs}}
- end
- end
-
- @impl true
- def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do
- # 6. Delete from the ETS table instead of the map
- {name, refs} = Map.pop(refs, ref)
- :ets.delete(names, name)
- {:noreply, {names, refs}}
- end
-
- @impl true
- def handle_info(_msg, state) do
- {:noreply, state}
- end
-end
-```
-
-Notice that before our changes `KV.Registry.lookup/2` sent requests to the server, but now it reads directly from the ETS table, which is shared across all processes. That's the main idea behind the cache mechanism we are implementing.
-
-In order for the cache mechanism to work, the created ETS table needs to have access `:protected` (the default), so all clients can read from it, while only the `KV.Registry` process writes to it. We have also set `read_concurrency: true` when starting the table, optimizing the table for the common scenario of concurrent read operations.
-
-The changes we have performed above have broken our tests because the registry requires the `:name` option when starting up. Furthermore, some registry operations such as `lookup/2` require the name to be given as an argument, instead of a PID, so we can do the ETS table lookup. Let's change the setup function in `test/kv/registry_test.exs` to fix both issues:
-
-```elixir
- setup context do
- _ = start_supervised!({KV.Registry, name: context.test})
- %{registry: context.test}
- end
-```
-
-Since each test has a unique name, we use the test name to name our registries. This way, we no longer need to pass the registry PID around, instead we identify it by the test name. Also note we assigned the result of `start_supervised!` to underscore (`_`). This idiom is often used to signal that we are not interested in the result of `start_supervised!`.
-
-Once we change `setup`, some tests will continue to fail. You may even notice tests pass and fail inconsistently between runs. For example, the "spawns buckets" test:
-
-```elixir
-test "spawns buckets", %{registry: registry} do
- assert KV.Registry.lookup(registry, "shopping") == :error
-
- KV.Registry.create(registry, "shopping")
- assert {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
-
- KV.Bucket.put(bucket, "milk", 1)
- assert KV.Bucket.get(bucket, "milk") == 1
-end
-```
-
-may be failing on this line:
-
-```elixir
-{:ok, bucket} = KV.Registry.lookup(registry, "shopping")
-```
-
-How can this line fail if we just created the bucket in the previous line?
-
-The reason those failures are happening is because, for didactic purposes, we have made two mistakes:
-
- 1. We are prematurely optimizing (by adding this cache layer)
- 2. We are using `cast/2` (while we should be using `call/2`)
-
-## Race conditions?
-
-Developing in Elixir does not make your code free of race conditions. However, Elixir's abstractions where nothing is shared by default make it easier to spot a race condition's root cause.
-
-What is happening in our tests is that there is a delay in between an operation and the time we can observe this change in the ETS table. Here is what we were expecting to happen:
-
-1. We invoke `KV.Registry.create(registry, "shopping")`
-2. The registry creates the bucket and updates the cache table
-3. We access the information from the table with `KV.Registry.lookup(registry, "shopping")`
-4. The command above returns `{:ok, bucket}`
-
-However, since `KV.Registry.create/2` is a cast operation, the command will return before we actually write to the table! In other words, this is happening:
-
-1. We invoke `KV.Registry.create(registry, "shopping")`
-2. We access the information from the table with `KV.Registry.lookup(registry, "shopping")`
-3. The command above returns `:error`
-4. The registry creates the bucket and updates the cache table
-
-To fix the failure we need to make `KV.Registry.create/2` synchronous by using `call/2` rather than `cast/2`. This will guarantee that the client will only continue after changes have been made to the table. Let's go back to `lib/kv/registry.ex` and change the function and its callback as follows:
-
-```elixir
-def create(server, name) do
- GenServer.call(server, {:create, name})
-end
-```
-```elixir
-@impl true
-def handle_call({:create, name}, _from, {names, refs}) do
- case lookup(names, name) do
- {:ok, pid} ->
- {:reply, pid, {names, refs}}
- :error ->
- {:ok, pid} = DynamicSupervisor.start_child(KV.BucketSupervisor, KV.Bucket)
- ref = Process.monitor(pid)
- refs = Map.put(refs, ref, name)
- :ets.insert(names, {name, pid})
- {:reply, pid, {names, refs}}
- end
-end
-```
-
-We changed the callback from `handle_cast/2` to `handle_call/3` and changed it to reply with the pid of the created bucket. Generally speaking, Elixir developers prefer to use `call/2` instead of `cast/2` as it also provides back-pressure - you block until you get a reply. Using `cast/2` when not necessary can also be considered a premature optimization.
-
-Let's run the tests once again. This time though, we will pass the `--trace` option:
-
-```console
-$ mix test --trace
-```
-
-The `--trace` option is useful when your tests are deadlocking or there are race conditions, as it runs all tests synchronously (`async: true` has no effect) and shows detailed information about each test. If you run the tests multiple times you may see this intermittent failure:
-
-```
- 1) test removes buckets on exit (KV.RegistryTest)
- test/kv/registry_test.exs:19
- Assertion with == failed
- code: assert KV.Registry.lookup(registry, "shopping") == :error
- left: {:ok, #PID<0.109.0>}
- right: :error
- stacktrace:
- test/kv/registry_test.exs:23
-```
-
-According to the failure message, we are expecting that the bucket no longer exists on the table, but it still does! This problem is the opposite of the one we have just solved: while previously there was a delay between the command to create a bucket and updating the table, now there is a delay between the bucket process dying and its entry being removed from the table. Since this is a race condition, you may not be able to reproduce it on your machine, but it is there.
-
-Last time we fixed the race condition by replacing the asynchronous operation, a `cast`, by a `call`, which is synchronous. Unfortunately, the `handle_info/2` callback we are using to receive the `:DOWN` message and delete the entry from the ETS table does not have a synchronous equivalent. This time, we need to find a way to guarantee the registry has processed the `:DOWN` notification sent when the bucket process terminated.
-
-An easy way to do so is by sending a synchronous request to the registry before we do the bucket lookup. The `Agent.stop/2` operation is synchronous and only returns after the bucket process terminates and all `:DOWN` messages are delivered. Therefore, once `Agent.stop/2` returns, the registry has already received the `:DOWN` message but it may not have processed it yet. In order to guarantee the processing of the `:DOWN` message, we can do a synchronous request. Since messages are processed in order, once the registry replies to the synchronous request, then the `:DOWN` message will definitely have been processed.
-
-Let's do so by creating a "bogus" bucket, which is a synchronous request, after `Agent.stop/2` in both "remove" tests at `test/kv/registry_test.exs`:
-
-```elixir
- test "removes buckets on exit", %{registry: registry} do
- KV.Registry.create(registry, "shopping")
- {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
- Agent.stop(bucket)
-
- # Do a call to ensure the registry processed the DOWN message
- _ = KV.Registry.create(registry, "bogus")
- assert KV.Registry.lookup(registry, "shopping") == :error
- end
-
- test "removes bucket on crash", %{registry: registry} do
- KV.Registry.create(registry, "shopping")
- {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
-
- # Stop the bucket with non-normal reason
- Agent.stop(bucket, :shutdown)
-
- # Do a call to ensure the registry processed the DOWN message
- _ = KV.Registry.create(registry, "bogus")
- assert KV.Registry.lookup(registry, "shopping") == :error
- end
-```
-
-Our tests should now (always) pass!
-
-Note that the purpose of the test is to check whether the registry processes the bucket's shutdown message correctly. The fact that the `KV.Registry.lookup/2` sends us a valid bucket does not mean that the bucket is still alive by the time you call it. For example, it might have crashed for some reason. The following test depicts this situation:
-
-```elixir
- test "bucket can crash at any time", %{registry: registry} do
- KV.Registry.create(registry, "shopping")
- {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
-
- # Simulate a bucket crash by explicitly and synchronously shutting it down
- Agent.stop(bucket, :shutdown)
-
- # Now trying to call the dead process causes a :noproc exit
- catch_exit KV.Bucket.put(bucket, "milk", 3)
- end
-```
-
-This concludes our optimization chapter. We have used ETS as a cache mechanism where reads can happen from any processes but writes are still serialized through a single process. More importantly, we have also learned that once data can be read asynchronously, we need to be aware of the race conditions it might introduce.
-
-In practice, if you find yourself in a position where you need a process registry for dynamic processes, you should use [the `Registry` module](https://hexdocs.pm/elixir/Registry.html) provided as part of Elixir. It provides functionality similar to the one we have built using a GenServer + `:ets` while also being able to perform both writes and reads concurrently. [It has been benchmarked to scale across all cores even on machines with 40 cores](https://elixir-lang.org/blog/2017/01/05/elixir-v1-4-0-released/).
-
-Next, let's discuss external and internal dependencies and how Mix helps us manage large codebases.
diff --git a/getting-started/mix-otp/genserver.markdown b/getting-started/mix-otp/genserver.markdown
index 9e6ff7652..0c548b6bb 100644
--- a/getting-started/mix-otp/genserver.markdown
+++ b/getting-started/mix-otp/genserver.markdown
@@ -1,345 +1,5 @@
---
-section: getting-started
-layout: getting-started
-title: GenServer
-category: Mix and OTP
+layout: redirect
+sitemap: false
+redirect_to: genservers
---
-
-{% include mix-otp-preface.html %}
-
-In the [previous chapter](/getting-started/mix-otp/agent.html), we used agents to represent our buckets. In the [introduction to mix](/getting-started/mix-otp/introduction-to-mix.html), we specified we would like to name each bucket so we can do the following:
-
-```elixir
-CREATE shopping
-OK
-
-PUT shopping milk 1
-OK
-
-GET shopping milk
-1
-OK
-```
-
-In the session above we interacted with the "shopping" bucket.
-
-Since agents are processes, each bucket has a process identifier (pid), but buckets do not have a name. Back [in the Process chapter](/getting-started/processes.html), we have learned that we can register processes in Elixir by giving them atom names:
-
-```elixir
-iex> Agent.start_link(fn -> %{} end, name: :shopping)
-{:ok, #PID<0.43.0>}
-iex> KV.Bucket.put(:shopping, "milk", 1)
-:ok
-iex> KV.Bucket.get(:shopping, "milk")
-1
-```
-
-However, naming dynamic processes with atoms is a terrible idea! If we use atoms, we would need to convert the bucket name (often received from an external client) to atoms, and **we should never convert user input to atoms**. This is because atoms are not garbage collected. Once an atom is created, it is never reclaimed. Generating atoms from user input would mean the user can inject enough different names to exhaust our system memory!
-
-In practice, it is more likely you will reach the Erlang VM limit for the maximum number of atoms before you run out of memory, which will bring your system down regardless.
-
-Instead of abusing the built-in name facility, we will create our own *process registry* that associates the bucket name to the bucket process.
-
-The registry needs to guarantee that it is always up to date. For example, if one of the bucket processes crashes due to a bug, the registry must notice this change and avoid serving stale entries. In Elixir, we say the registry needs to *monitor* each bucket. Because our *registry* needs to be able to receive and handle ad-hoc messages from the system, the `Agent` API is not enough.
-
-We will use a [GenServer](https://hexdocs.pm/elixir/GenServer.html) to create a registry process that can monitor the bucket processes. GenServer provides industrial strength functionality for building servers in both Elixir and OTP.
-
-Please read [the GenServer module documentation](https://hexdocs.pm/elixir/GenServer.html) for an overview if you haven't yet. Once you do so, we are ready to proceed.
-
-## GenServer callbacks
-
-A GenServer is a process that invokes a limited set of functions under specific conditions. When we used an `Agent`, we would keep both the client code and the server code side by side, like this:
-
-```elixir
-def put(bucket, key, value) do
- Agent.update(bucket, &Map.put(&1, key, value))
-end
-```
-
-Let's break that code apart a bit:
-
-```elixir
-def put(bucket, key, value) do
- # Here is the client code
- Agent.update(bucket, fn state ->
- # Here is the server code
- Map.put(state, key, value)
- end)
- # Back to the client code
-end
-```
-
-In the code above, we have a process, which we call "the client" sending a request to an agent, "the server". The request contains an anonymous function, which must be executed by the server.
-
-In a GenServer, the code above would be two separate functions, roughly like this:
-
-```elixir
-def put(bucket, key, value) do
- # Send the server a :put "instruction"
- GenServer.call(bucket, {:put, key, value})
-end
-
-# Server callback
-
-def handle_call({:put, key, value}, _from, state) do
- {:reply, :ok, Map.put(state, key, value)}
-end
-```
-
-There is quite a bit more ceremony in the GenServer code but, as we will see, it brings some benefits too.
-
-For now, we will write only the server callbacks for our bucket registering logic, without providing a proper API, which we will do later.
-
-Create a new file at `lib/kv/registry.ex` with the following contents:
-
-```elixir
-defmodule KV.Registry do
- use GenServer
-
- ## Missing Client API - will add this later
-
- ## Defining GenServer Callbacks
-
- @impl true
- def init(:ok) do
- {:ok, %{}}
- end
-
- @impl true
- def handle_call({:lookup, name}, _from, names) do
- {:reply, Map.fetch(names, name), names}
- end
-
- @impl true
- def handle_cast({:create, name}, names) do
- if Map.has_key?(names, name) do
- {:noreply, names}
- else
- {:ok, bucket} = KV.Bucket.start_link([])
- {:noreply, Map.put(names, name, bucket)}
- end
- end
-end
-```
-
-There are two types of requests you can send to a GenServer: calls and casts. Calls are synchronous and the server **must** send a response back to such requests. While the server computes the response, the client is **waiting**. Casts are asynchronous: the server won't send a response back and therefore the client won't wait for one. Both requests are messages sent to the server, and will be handled in sequence. In the above implementation, we pattern-match on the `:create` messages, to be handled as cast, and on the `:lookup` messages, to be handled as call.
-
-In order to invoke the callbacks above, we need to go through the corresponding `GenServer` functions. Let's start a registry, create a named bucket, and then look it up:
-
-```elixir
-iex> {:ok, registry} = GenServer.start_link(KV.Registry, :ok)
-{:ok, #PID<0.136.0>}
-iex> GenServer.cast(registry, {:create, "shopping"})
-:ok
-iex> {:ok, bk} = GenServer.call(registry, {:lookup, "shopping"})
-{:ok, #PID<0.174.0>}
-```
-
-Our `KV.Registry` process received a cast with `{:create, "shopping"}` and a call with `{:lookup, "shopping"}`, in this sequence. `GenServer.cast` will immediately return, as soon as the message is sent to the `registry`. The `GenServer.call` on the other hand, is where we would be waiting for an answer, provided by the above `KV.Registry.handle_call` callback.
-
-You may also have noticed that we have added `@impl true` before each callback. The `@impl true` informs the compiler that our intention for the subsequent function definition is to define a callback. If by any chance we make a mistake in the function name or in the number of arguments, like we define a `handle_call/2`, the compiler would warn us there isn't any `handle_call/2` to define, and would give us the complete list of known callbacks for the `GenServer` module.
-
-This is all good and well, but we still want to offer our users an API that allows us to hide our implementation details.
-
-## The Client API
-
-A GenServer is implemented in two parts: the client API and the server callbacks. You can either combine both parts into a single module or you can separate them into a client module and a server module. The client is any process that invokes the client function. The server is always the process identifier or process name that we will explicitly pass as argument to the client API. Here we'll use a single module for both the server callbacks and the client API.
-
-Edit the file at `lib/kv/registry.ex`, filling in the blanks for the client API:
-
-```elixir
- ## Client API
-
- @doc """
- Starts the registry.
- """
- def start_link(opts) do
- GenServer.start_link(__MODULE__, :ok, opts)
- end
-
- @doc """
- Looks up the bucket pid for `name` stored in `server`.
-
- Returns `{:ok, pid}` if the bucket exists, `:error` otherwise.
- """
- def lookup(server, name) do
- GenServer.call(server, {:lookup, name})
- end
-
- @doc """
- Ensures there is a bucket associated with the given `name` in `server`.
- """
- def create(server, name) do
- GenServer.cast(server, {:create, name})
- end
-```
-
-The first function is `start_link/1`, which starts a new GenServer passing a list of options. `start_link/1` calls out to `GenServer.start_link/3`, which takes three arguments:
-
-1. The module where the server callbacks are implemented, in this case `__MODULE__` (meaning the current module)
-
-2. The initialization arguments, in this case the atom `:ok`
-
-3. A list of options which can be used to specify things like the name of the server. For now, we forward the list of options that we receive on `start_link/1` to `GenServer.start_link/3`
-
-The next two functions, `lookup/2` and `create/2`, are responsible for sending these requests to the server. In this case, we have used `{:lookup, name}` and `{:create, name}` respectively. Requests are often specified as tuples, like this, in order to provide more than one "argument" in that first argument slot. It's common to specify the action being requested as the first element of a tuple, and arguments for that action in the remaining elements. Note that the requests must match the first argument to `handle_call/3` or `handle_cast/2`.
-
-That's it for the client API. On the server side, we can implement a variety of callbacks to guarantee the server initialization, termination, and handling of requests. Those callbacks are optional and for now, we have only implemented the ones we care about. Let's recap.
-
-The first is the `init/1` callback, that receives the second argument given to `GenServer.start_link/3` and returns `{:ok, state}`, where state is a new map. We can already notice how the `GenServer` API makes the client/server segregation more apparent. `start_link/3` happens in the client, while `init/1` is the respective callback that runs on the server.
-
-For `call/2` requests, we implement a `handle_call/3` callback that receives the `request`, the process from which we received the request (`_from`), and the current server state (`names`). The `handle_call/3` callback returns a tuple in the format `{:reply, reply, new_state}`. The first element of the tuple, `:reply`, indicates that the server should send a reply back to the client. The second element, `reply`, is what will be sent to the client while the third, `new_state` is the new server state.
-
-For `cast/2` requests, we implement a `handle_cast/2` callback that receives the `request` and the current server state (`names`). The `handle_cast/2` callback returns a tuple in the format `{:noreply, new_state}`. Note that in a real application we would have probably implemented the callback for `:create` with a synchronous call instead of an asynchronous cast. We are doing it this way to illustrate how to implement a cast callback.
-
-There are other tuple formats both `handle_call/3` and `handle_cast/2` callbacks may return. There are also other callbacks like `terminate/2` and `code_change/3` that we could implement. You are welcome to explore the [full GenServer documentation](https://hexdocs.pm/elixir/GenServer.html) to learn more about those.
-
-For now, let's write some tests to guarantee our GenServer works as expected.
-
-## Testing a GenServer
-
-Testing a GenServer is not much different from testing an agent. We will spawn the server on a setup callback and use it throughout our tests. Create a file at `test/kv/registry_test.exs` with the following:
-
-```elixir
-defmodule KV.RegistryTest do
- use ExUnit.Case, async: true
-
- setup do
- registry = start_supervised!(KV.Registry)
- %{registry: registry}
- end
-
- test "spawns buckets", %{registry: registry} do
- assert KV.Registry.lookup(registry, "shopping") == :error
-
- KV.Registry.create(registry, "shopping")
- assert {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
-
- KV.Bucket.put(bucket, "milk", 1)
- assert KV.Bucket.get(bucket, "milk") == 1
- end
-end
-```
-
-Our test case first asserts there's no buckets in our registry, creates a named bucket, looks it up, and asserts it behaves as a bucket.
-
-There is one important difference between the `setup` block we wrote for `KV.Registry` and the one we wrote for `KV.Bucket`. Instead of starting the registry by hand by calling `KV.Registry.start_link/1`, we instead called [the `start_supervised!/2` function](https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#start_supervised/2), passing the `KV.Registry` module.
-
-The `start_supervised!` function was injected into our test module by `use ExUnit.Case`. It does the job of starting the `KV.Registry` process, by calling its `start_link/1` function. The advantage of using `start_supervised!` is that ExUnit will guarantee that the registry process will be shutdown **before** the next test starts. In other words, it helps guarantee that the state of one test is not going to interfere with the next one in case they depend on shared resources.
-
-When starting processes during your tests, we should always prefer to use `start_supervised!`. We recommend you to change the `setup` block in `bucket_test.exs` to use `start_supervised!` too.
-
-Run the tests and they should all pass!
-
-## The need for monitoring
-
-Everything we have done so far could have been implemented with an `Agent`. In this section, we will see one of many things that we can achieve with a GenServer that is not possible with an Agent.
-
-Let's start with a test that describes how we want the registry to behave if a bucket stops or crashes:
-
-```elixir
-test "removes buckets on exit", %{registry: registry} do
- KV.Registry.create(registry, "shopping")
- {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
- Agent.stop(bucket)
- assert KV.Registry.lookup(registry, "shopping") == :error
-end
-```
-
-The test above will fail on the last assertion as the bucket name remains in the registry even after we stop the bucket process.
-
-In order to fix this bug, we need the registry to monitor every bucket it spawns. Once we set up a monitor, the registry will receive a notification every time a bucket process exits, allowing us to clean the registry up.
-
-Let's first play with monitors by starting a new console with `iex -S mix`:
-
-```elixir
-iex> {:ok, pid} = KV.Bucket.start_link([])
-{:ok, #PID<0.66.0>}
-iex> Process.monitor(pid)
-#Reference<0.0.0.551>
-iex> Agent.stop(pid)
-:ok
-iex> flush()
-{:DOWN, #Reference<0.0.0.551>, :process, #PID<0.66.0>, :normal}
-```
-
-Note `Process.monitor(pid)` returns a unique reference that allows us to match upcoming messages to that monitoring reference. After we stop the agent, we can `flush/0` all messages and notice a `:DOWN` message arrived, with the exact reference returned by `monitor`, notifying that the bucket process exited with reason `:normal`.
-
-Let's reimplement the server callbacks to fix the bug and make the test pass. First, we will modify the GenServer state to two dictionaries: one that contains `name -> pid` and another that holds `ref -> name`. Then we need to monitor the buckets on `handle_cast/2` as well as implement a `handle_info/2` callback to handle the monitoring messages. The full server callbacks implementation is shown below:
-
-```elixir
-## Server callbacks
-
-@impl true
-def init(:ok) do
- names = %{}
- refs = %{}
- {:ok, {names, refs}}
-end
-
-@impl true
-def handle_call({:lookup, name}, _from, state) do
- {names, _} = state
- {:reply, Map.fetch(names, name), state}
-end
-
-@impl true
-def handle_cast({:create, name}, {names, refs}) do
- if Map.has_key?(names, name) do
- {:noreply, {names, refs}}
- else
- {:ok, bucket} = KV.Bucket.start_link([])
- ref = Process.monitor(bucket)
- refs = Map.put(refs, ref, name)
- names = Map.put(names, name, bucket)
- {:noreply, {names, refs}}
- end
-end
-
-@impl true
-def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do
- {name, refs} = Map.pop(refs, ref)
- names = Map.delete(names, name)
- {:noreply, {names, refs}}
-end
-
-@impl true
-def handle_info(msg, state) do
- require Logger
- Logger.debug("Unexpected message in KV.Registry: #{inspect(msg)}")
- {:noreply, state}
-end
-```
-
-Observe that we were able to considerably change the server implementation without changing any of the client API. That's one of the benefits of explicitly segregating the server and the client.
-
-Finally, different from the other callbacks, we have defined a "catch-all" clause for `handle_info/2` that discards and logs any unknown message. To understand why, let's move on to the next section.
-
-## `call`, `cast` or `info`?
-
-So far we have used three callbacks: `handle_call/3`, `handle_cast/2` and `handle_info/2`. Here is what we should consider when deciding when to use each:
-
-1. `handle_call/3` must be used for synchronous requests. This should be the default choice as waiting for the server reply is a useful backpressure mechanism.
-
-2. `handle_cast/2` must be used for asynchronous requests, when you don't care about a reply. A cast does not guarantee the server has received the message and, for this reason, should be used sparingly. For example, the `create/2` function we have defined in this chapter should have used `call/2`. We have used `cast/2` for didactic purposes.
-
-3. `handle_info/2` must be used for all other messages a server may receive that are not sent via `GenServer.call/2` or `GenServer.cast/2`, including regular messages sent with `send/2`. The monitoring `:DOWN` messages are an example of this.
-
-Since any message, including the ones sent via `send/2`, go to `handle_info/2`, there is a chance unexpected messages will arrive to the server. Therefore, if we don't define the catch-all clause, those messages could cause our registry to crash, because no clause would match. We don't need to worry about such cases for `handle_call/3` and `handle_cast/2` though. Calls and casts are only done via the `GenServer` API, so an unknown message is quite likely a developer mistake.
-
-To help developers remember the differences between call, cast and info, the supported return values and more, we have a tiny [GenServer cheat sheet](/downloads/cheatsheets/gen-server.pdf).
-
-## Monitors or links?
-
-We have previously learned about links in the [Process chapter](/getting-started/processes.html). Now, with the registry complete, you may be wondering: when should we use monitors and when should we use links?
-
-Links are bi-directional. If you link two processes and one of them crashes, the other side will crash too (unless it is trapping exits). A monitor is uni-directional: only the monitoring process will receive notifications about the monitored one. In other words: use links when you want linked crashes, and monitors when you just want to be informed of crashes, exits, and so on.
-
-Returning to our `handle_cast/2` implementation, you can see the registry is both linking and monitoring the buckets:
-
-```elixir
-{:ok, bucket} = KV.Bucket.start_link([])
-ref = Process.monitor(bucket)
-```
-
-This is a bad idea, as we don't want the registry to crash when a bucket crashes. The proper fix is to actually not link the bucket to the registry. Instead, we will link each bucket to a special type of process called Supervisors, which are explicitly designed to handle failures and crashes. We will learn more about them in the next chapter.
diff --git a/getting-started/mix-otp/index.html b/getting-started/mix-otp/index.html
index b2f271a12..6406e2fed 100644
--- a/getting-started/mix-otp/index.html
+++ b/getting-started/mix-otp/index.html
@@ -1,7 +1,5 @@
-
-
-
- Check our getting started guide and our learning page to begin your journey with Elixir. Or keep scrolling for an overview of the platform, language, and tools. +
Check our Getting Started guide and our Learning page to begin your journey with Elixir. Or keep scrolling for an overview of the platform, language, and tools.
Elixir has been designed to be extensible, allowing developers naturally extend the language to particular domains, in order to increase their productivity.
+Elixir has been designed to be extensible, allowing developers to naturally extend the language to particular domains, in order to increase their productivity.
As an example, let's write a simple test case using Elixir's test framework called ExUnit:
@@ -195,7 +196,7 @@To learn more about Elixir, check our getting started guide. We also have online documentation available and a Crash Course for Erlang developers.
+To learn more about Elixir, check our Getting Started guide.
Elixir in Action is a tutorial book that aims to bring developers new to Elixir and Erlang to the point where they can develop complex systems on their own. No knowledge about Elixir, Erlang, or functional programming is required, but it is assumed that a reader has a few years of production experience using mainstream OO languages, for example C#, Java, Python, or Ruby.
@@ -27,7 +28,7 @@ The book starts with a basic introduction to the Elixir language and functional
This book is the introduction to Elixir for experienced programmers, completely updated for Elixir 1.6 and beyond. Explore functional programming without the academic overtones (tell me about monads just one more time). Create concurrent applications, but get them right without all the locking and consistency headaches.
@@ -38,7 +39,7 @@ Meet Elixir, a modern, functional, concurrent language built on the rock-solid E
Adoption is more than programming. Elixir is an exciting new language, but to successfully get your application from start to finish, you're going to need to know more than just the language. You need the case studies and strategies in this book.
@@ -59,7 +60,7 @@ This book will teach you the core concepts of the Elixir programming language in
Elixir's straightforward syntax and this guided tour give you a clean, simple path to learn modern functional programming techniques. No previous functional programming experience required! This book walks you through the right concepts at the right pace, as you explore immutable values and explicit data transformation, functions, modules, recursive functions, pattern matching, high-order functions, polymorphism, and failure handling, all while avoiding side effects. Don't board the Elixir train with an imperative mindset! To get the most out of functional languages, you need to think functionally. This book will get you there.
@@ -88,7 +89,7 @@ If you're a new Elixir developer who's gone through some basic Elixir tutorials
Write code that writes code with Elixir macros. Macros make metaprogramming possible and define the language itself. In this book, you'll learn how to use macros to extend the language with fast, maintainable code and share functionality in ways you never thought possible. You'll discover how to extend Elixir with your own first-class features, optimize performance, and create domain-specific languages.
@@ -97,7 +98,7 @@ Write code that writes code with Elixir macros. Macros make metaprogramming poss
You know how to code in Elixir; now learn to think in it. Learn to design libraries with intelligent layers that shape the right data structures, flow from one function into the next, and present the right APIs. Embrace the same OTP that's kept our telephone systems reliable and fast for over 30 years. Move beyond understanding the OTP functions to knowing what's happening under the hood, and why that matters. Using that knowledge, instinctively know how to design systems that deliver fast and resilient services to your users, all with an Elixir focus.
@@ -210,8 +211,31 @@ Found your start-up, migrate a codebase, build that app! Our midterm and final p
+
+
+
+TechSchool is an open-source platform that teaches programming through free YouTube videos and other websites. The goal is to make technology education accessible to everyone. It includes several Elixir courses and a complete Fullstack Elixir + Phoenix Bootcamp.
+
+
+
## Screencasts
+
+
+
+ElixirStreams provides free video tips (under 3 mins!) covering a variety of
+Elixir and Phoenix topics. The videos help you sharpen the saw as you learn
+about new tools and tricks, and they keep you up to date with the latest
+developments in the language.
+
+
+
+
Elixir koans is a fun, easy way to get started with the Elixir programming language. It is an idiomatic tour of the language.
-
+