diff --git a/lib/elixir/pages/getting-started/basic-types.md b/lib/elixir/pages/getting-started/basic-types.md new file mode 100644 index 0000000000..beea13a15f --- /dev/null +++ b/lib/elixir/pages/getting-started/basic-types.md @@ -0,0 +1,330 @@ +# Basic types + +In this chapter we will learn more about Elixir basic types: integers, floats, booleans, atoms, and strings. Other data types, such as lists and tuples, will be explored in the next chapter. + +```elixir +iex> 1 # integer +iex> 0x1F # integer +iex> 1.0 # float +iex> true # boolean +iex> :atom # atom / symbol +iex> "elixir" # string +iex> [1, 2, 3] # list +iex> {1, 2, 3} # tuple +``` + +## Basic arithmetic + +Open up `iex` and type the following expressions: + +```elixir +iex> 1 + 2 +3 +iex> 5 * 5 +25 +iex> 10 / 2 +5.0 +``` + +Notice that `10 / 2` returned a float `5.0` instead of an integer `5`. This is expected. In Elixir, the operator `/` always returns a float. If you want to do integer division or get the division remainder, you can invoke the `div` and `rem` functions: + +```elixir +iex> div(10, 2) +5 +iex> div 10, 2 +5 +iex> rem 10, 3 +1 +``` + +Notice that Elixir allows you to drop the parentheses when invoking functions that expect one or more arguments. This feature gives a cleaner syntax when writing declarations and control-flow constructs. However, Elixir developers generally prefer to use parentheses. + +Elixir also supports shortcut notations for entering binary, octal, and hexadecimal numbers: + +```elixir +iex> 0b1010 +10 +iex> 0o777 +511 +iex> 0x1F +31 +``` + +Float numbers require a dot followed by at least one digit and also support `e` for scientific notation: + +```elixir +iex> 1.0 +1.0 +iex> 1.0e-10 +1.0e-10 +``` + +Floats in Elixir are 64-bit precision. + +You can invoke the `round` function to get the closest integer to a given float, or the `trunc` function to get the integer part of a float. + +```elixir +iex> round(3.58) +4 +iex> trunc(3.58) +3 +``` + +Finally, we work with different data types, we will learn Elixir provides several predicate functions to check for the type of a value. For example, the `is_integer` can be used to check if a value is an integer or not: + +```elixir +iex> is_integer(1) +true +iex> is_integer(2.0) +false +``` + +You can also use `is_float` or `is_number` to check, respectively, if an argument is an a float, or either an integer or float. + +## Identifying functions and documentation + +Before we move on to the next data type, let's talk about how Elixir identity functions. + +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`. + +We can also use this syntax to access documentation. The Elixir shell defines the `h` function, which you can use to access documentation for any function. For example, typing `h trunc/1` is going to print the documentation for the `trunc/1` function: + +```elixir +iex> h trunc/1 + def trunc() + +Returns the integer part of number. +``` + +`h trunc/1` works because it is defined in the `Kernel` module. All functions in the `Kernel` module are automatically imported into our namespace. Most often you will also include the module name when looking up for documentation for a given function: + +```elixir +iex> h Kernel.trunc/1 + def trunc() + +Returns the integer part of number. +``` + +You can use the module+function to lookup for anything, including operators (try `h Kernel.+/2`). Invoking `h` without arguments displays the documentation for `IEx.Helpers`, which is where `h` and other functionality is defined. + +## Booleans and `nil` + +Elixir supports `true` and `false` as booleans: + +```elixir +iex> true +true +iex> true == false +false +``` + +Elixir also provides three boolean operators: `or/2`, `and/2`, and `not/1`. These operators are strict in the sense that they expect something that evaluates to a boolean (`true` or `false`) as their first argument: + +```elixir +iex> true and true +true +iex> false or is_boolean(true) +true +``` + +Providing a non-boolean will raise an exception: + +```elixir +iex> 1 and true +** (BadBooleanError) expected a boolean on left-side of "and", got: 1 +``` + +`or` and `and` are short-circuit operators. They only execute the right side if the left side is not enough to determine the result: + +```elixir +iex> false and raise("This error will never be raised") +false +iex> true or raise("This error will never be raised") +true +``` + +Elixir also provides the concept of `nil`, to indicate the absence of a value, and a set of logical operators that also manipulate `nil`: `||/2`, `&&/2`, and `!/1`. For these operators, `false` and `nil` are considered "falsy", all other valuesare considered "truthy": + +```elixir +# or +iex> 1 || true +1 +iex> false || 11 +11 + +# and +iex> nil && 13 +nil +iex> true && 17 +17 + +# not +iex> !true +false +iex> !1 +false +iex> !nil +true +``` + +## Atoms + +An atom is a constant whose value is its own name. Some other languages call these symbols. They are often useful to enumerate over distinct values, such as: + +```elixir +iex> :apple +:apple +iex> :orange +:orange +iex> :watermelon +:watermelon +``` + +Atoms are equal if their names are equal. + +```elixir +iex> :apple == :apple +true +iex> :apple == :orange +false +``` + +Often they are used to express the state of an operation, by using values such as `:ok` and `:error`. + +The booleans `true` and `false` are also atoms: + +```elixir +iex> true == :true +true +iex> is_atom(false) +true +iex> is_boolean(:false) +true +``` + +Elixir allows you to skip the leading `:` for the atoms `false`, `true` and `nil`. + +## Strings + +Strings in Elixir are delimited by double quotes, and they are encoded in UTF-8: + +```elixir +iex> "hellö" +"hellö" +``` + +> Note: if you are running on Windows, there is a chance your terminal does not use UTF-8 by default. You can change the encoding of your current session by running `chcp 65001` before entering IEx. + +You can concatenate two strings with the `<>/2` operator: + +```elixir +iex> "hello " <> "world!" +"hello world!" +``` + +Elixir also supports string interpolation: + +```elixir +iex> string = "world" +iex> "hello #{string}!" +"hello world" +``` + +String concatenation requires both sides to be strings but interpolation supports any data type that may be converted to a string: + +```elixir +iex> number = 42 +iex> "i am #{number} years old!" +"i am 42 years old!" +``` + +Strings can have line breaks in them. You can introduce them using escape sequences: + +```elixir +iex> "hello +...> world" +"hello\nworld" +iex> "hello\nworld" +"hello\nworld" +``` + +You can print a string using the `IO.puts/1` function from the `IO` module: + +```elixir +iex> IO.puts("hello\nworld") +hello +world +:ok +``` + +Notice that the `IO.puts/1` function returns the atom `:ok` after printing. + +Strings in Elixir are represented internally by contiguous sequences of bytes known as binaries: + +```elixir +iex> is_binary("hellö") +true +``` + +We can also get the number of bytes in a string: + +```elixir +iex> byte_size("hellö") +6 +``` + +Notice that the number of bytes in that string is 6, even though it has 5 graphemes. That's because the grapheme "ö" takes 2 bytes to be represented in UTF-8. We can get the actual length of the string, based on the number of graphemes, by using the `String.length/1` function: + +```elixir +iex> String.length("hellö") +5 +``` + +The `String` module contains a bunch of functions that operate on strings as defined in the Unicode standard: + +```elixir +iex> String.upcase("hellö") +"HELLÖ" +``` + +## Structural comparison + +Elixir also provides `==`, `!=`, `<=`, `>=`, `<` and `>` as comparison operators. We can compare numbers: + +```elixir +iex> 1 == 1 +true +iex> 1 != 2 +true +iex> 1 < 2 +true +``` + +But also atoms, strings, booleans, etc: + +```elixir +iex> "foo" == "foo" +true +iex> "foo" == "bar" +false +``` + +Integers and floats compare the same if they have the same value: + +```elixir +iex> 1 == 1.0 +true +iex> 1 == 2.0 +false +``` + +However, you can use the strict comparison operator `===` and `!==` if you want to distinguish between integers and floats (that's the only difference between these operators): + +```elixir +iex> 1 === 1.0 +false +``` + +The comparison operators in Elixir can compare across any data type. We say these operators perform _structural comparison_. For more information, you can read our documentation on [Structural vs Semantic comparisons](https://hexdocs.pm/elixir/Kernel.html#module-structural-comparison). + +Elixir also provides data-types for expressing collections, such as lists and tuples, which we learn next. When we talk about concurrency and fault-tolerance via processes, we will also discuss ports, pids, and references, but that will come on later chapters. Let's move forward. diff --git a/lib/elixir/pages/getting-started/introduction.md b/lib/elixir/pages/getting-started/introduction.md new file mode 100644 index 0000000000..90b8ecfa31 --- /dev/null +++ b/lib/elixir/pages/getting-started/introduction.md @@ -0,0 +1,59 @@ +# Introduction + +Welcome! + +This guide will teach you about Elixir fundamentals - the language syntax, how to define modules, the common data structures in the language, and more. This chapter will focus on ensuring that Elixir is installed and that you can successfully run Elixir's Interactive Shell, called IEx. + +Let's get started. + +> If you find any errors in the documentation, you can submit fixes by clicking the "source code" icon on the top right of every page and then clicking the "Edit" button. + +## Installation + +If you haven't yet installed Elixir, visit our [installation page](https://elixir-lang.org/install.html). Once you are done, you can run `elixir --version` to get the current Elixir version. The requirements for this guide are: + + * Elixir 1.15.0 onwards + * Erlang/OTP 26 onwards + +If you are looking for other resources for learning Elixir, you can also consult the [learning page](https://elixir-lang.org/learning.html) of the official website. + +## Interactive mode + +When you install Elixir, you will have three new command line executables: `iex`, `elixir` and `elixirc`. + +For now, let's start by running `iex` (or `iex.bat` if you are on Windows PowerShell, where `iex` is a PowerShell command) which stands for Interactive Elixir. In interactive mode, we can type any Elixir expression and get its result. Let's warm up with some basic expressions. + +Open up `iex` and type the following expressions: + +```elixir +Erlang/OTP 26 [64-bit] [smp:2:2] [...] + +Interactive Elixir - press Ctrl+C to exit +iex(1)> 40 + 2 +42 +iex(2)> "hello" <> " world" +"hello world" +``` + +Please note that some details like version numbers may differ a bit in your session, that's not important. By executing the code above, you should evaluate expressions and see their results. To exit `iex` press `Ctrl+C` twice. + +It seems we are ready to go! We will use the interactive shell quite a lot in the next chapters to get a bit more familiar with the language constructs and basic types, starting in the next chapter. + +> Note: if you are on Windows and running on an Erlang/OTP version earlier than 26, you can also try `iex --werl` (`iex.bat --werl` on PowerShell) which may provide a better experience depending on which console you are using. + +## Running scripts + +After getting familiar with the basics of the language you may want to try writing simple programs. This can be accomplished by putting the following Elixir code into a file: + +```elixir +IO.puts("Hello world from Elixir") +``` + +Save it as `simple.exs` and execute it with `elixir`: + +```console +$ elixir simple.exs +Hello world from Elixir +``` + +Later on we will learn [how to compile Elixir code](modules-and-functions.md) and how to create and work within Elixir projects using the Mix build tool. For now, let's move on to learn the basic data types in the language. diff --git a/lib/elixir/pages/getting-started/lists-and-tuples.md b/lib/elixir/pages/getting-started/lists-and-tuples.md new file mode 100644 index 0000000000..64ec6f56cf --- /dev/null +++ b/lib/elixir/pages/getting-started/lists-and-tuples.md @@ -0,0 +1,170 @@ +# Lists and tuples + +In this chapter we will learn two of the most used collection data-types in Elixir: lists and tuples. + +## (Linked) Lists + +Elixir uses square brackets to specify a list of values. Values can be of any type: + +```elixir +iex> [1, 2, true, 3] +[1, 2, true, 3] +iex> length([1, 2, 3]) +3 +``` + +Two lists can be concatenated or subtracted using the `++/2` and `--/2` operators respectively: + +```elixir +iex> [1, 2, 3] ++ [4, 5, 6] +[1, 2, 3, 4, 5, 6] +iex> [1, true, 2, false, 3, true] -- [true, false] +[1, 2, 3, true] +``` + +List operators never modify the existing list. Concatenating to or removing elements from a list returns a new list. We say that Elixir data structures are *immutable*. One advantage of immutability is that it leads to clearer code. You can freely pass the data around with the guarantee no one will mutate it in memory - only transform it. + +Throughout the tutorial, we will talk a lot about the head and tail of a list. The head is the first element of a list and the tail is the remainder of the list. They can be retrieved with the functions `hd/1` and `tl/1`. Let's assign a list to a variable and retrieve its head and tail: + +```elixir +iex> list = [1, 2, 3] +iex> hd(list) +1 +iex> tl(list) +[2, 3] +``` + +Getting the head or the tail of an empty list throws an error: + +```elixir +iex> hd([]) +** (ArgumentError) argument error +``` + +Sometimes you will create a list and it will return a quoted value preceded by `~c`. For example: + +```elixir +iex> [11, 12, 13] +~c"\v\f\r" +iex> [104, 101, 108, 108, 111] +~c"hello" +``` + +When Elixir sees a list of printable ASCII numbers, Elixir will print that as a charlist (literally a list of characters). Charlists are quite common when interfacing with existing Erlang code. Whenever you see a value in IEx and you are not quite sure what it is, you can use the `i/1` to retrieve information about it: + +```elixir +iex> i ~c"hello" +Term + i ~c"hello" +Data type + List +Description + ... +Raw representation + [104, 101, 108, 108, 111] +Reference modules + List +Implemented protocols + ... +``` + +We will talk more about charlists in the ["Binaries, strings, and charlists"](binaries-strings-and-char-lists.md) chapter. + +> #### Single-quoted strings {: .info} +> +> In Elixir, you can also use `'hello'` to build charlists, but this notation has been soft-deprecated in Elixir v1.15 and will emit warnings in future versions. Prefer to write `~c"hello"` instead. + +## Tuples + +Elixir uses curly brackets to define tuples. Like lists, tuples can hold any value: + +```elixir +iex> {:ok, "hello"} +{:ok, "hello"} +iex> tuple_size({:ok, "hello"}) +2 +``` + +Tuples store elements contiguously in memory. This means accessing a tuple element by index or getting the tuple size is a fast operation. Indexes start from zero: + +```elixir +iex> tuple = {:ok, "hello"} +{:ok, "hello"} +iex> elem(tuple, 1) +"hello" +iex> tuple_size(tuple) +2 +``` + +It is also possible to put an element at a particular index in a tuple with `put_elem/3`: + +```elixir +iex> tuple = {:ok, "hello"} +{:ok, "hello"} +iex> put_elem(tuple, 1, "world") +{:ok, "world"} +iex> tuple +{:ok, "hello"} +``` + +Notice that `put_elem/3` returned a new tuple. The original tuple stored in the `tuple` variable was not modified. Like lists, tuples are also immutable. Every operation on a tuple returns a new tuple, it never changes the given one. + +## Lists or tuples? + +What is the difference between lists and tuples? + +Lists are stored in memory as linked lists, meaning that each element in a list holds its value and points to the following element until the end of the list is reached. This means accessing the length of a list is a linear operation: we need to traverse the whole list in order to figure out its size. + +Similarly, the performance of list concatenation depends on the length of the left-hand list: + +```elixir +iex> list = [1, 2, 3] +[1, 2, 3] + +# This is fast as we only need to traverse `[0]` to prepend to `list` +iex> [0] ++ list +[0, 1, 2, 3] + +# This is slow as we need to traverse `list` to append 4 +iex> list ++ [4] +[1, 2, 3, 4] +``` + +Tuples, on the other hand, are stored contiguously in memory. This means getting the tuple size or accessing an element by index is fast. However, updating or adding elements to tuples is expensive because it requires creating a new tuple in memory: + +```elixir +iex> tuple = {:a, :b, :c, :d} +{:a, :b, :c, :d} +iex> put_elem(tuple, 2, :e) +{:a, :b, :e, :d} +``` + +Note that this applies only to the tuple itself, not its contents. For instance, when you update a tuple, all entries are shared between the old and the new tuple, except for the entry that has been replaced. In other words, tuples and lists in Elixir are capable of sharing their contents. This reduces the amount of memory allocation the language needs to perform and is only possible thanks to the immutable semantics of the language. + +Those performance characteristics dictate the usage of those data structures. One very common use case for tuples is to use them to return extra information from a function. For example, `File.read/1` is a function that can be used to read file contents. It returns a tuple: + +```elixir +iex> File.read("path/to/existing/file") +{:ok, "... contents ..."} +iex> File.read("path/to/unknown/file") +{:error, :enoent} +``` + +If the path given to `File.read/1` exists, it returns a tuple with the atom `:ok` as the first element and the file contents as the second. Otherwise, it returns a tuple with `:error` and the error description. + +Most of the time, Elixir is going to guide you to do the right thing. For example, there is an `elem/2` function to access a tuple item but there is no built-in equivalent for lists: + +```elixir +iex> tuple = {:ok, "hello"} +{:ok, "hello"} +iex> elem(tuple, 1) +"hello" +``` + +## Size or length? + +When counting the elements in a data structure, Elixir also abides by a simple rule: the function is named `size` if the operation is in constant time (the value is pre-calculated) or `length` if the operation is linear (calculating the length gets slower as the input grows). As a mnemonic, both "length" and "linear" start with "l". + +For example, we have used 4 counting functions so far: `byte_size/1` (for the number of bytes in a string), `tuple_size/1` (for tuple size), `length/1` (for list length) and `String.length/1` (for the number of graphemes in a string). We use `byte_size` to get the number of bytes in a string, which is a cheap operation. Retrieving the number of Unicode graphemes, on the other hand, uses `String.length/1`, and may be expensive as it relies on a traversal of the entire string. + +Now that we are familiar with the basic data-types in the language, let's learn important constructs for writing code, before we discuss more complex data structures. diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index df3e7d7285..231ac382f3 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -3,6 +3,9 @@ canonical = System.fetch_env!("CANONICAL") [ extras: [ + "lib/elixir/pages/getting-started/introduction.md", + "lib/elixir/pages/getting-started/basic-types.md", + "lib/elixir/pages/getting-started/lists-and-tuples.md", "lib/elixir/pages/anti-patterns/what-anti-patterns.md", "lib/elixir/pages/anti-patterns/code-anti-patterns.md", "lib/elixir/pages/anti-patterns/design-anti-patterns.md", @@ -30,6 +33,7 @@ canonical = System.fetch_env!("CANONICAL") mix: "https://hexdocs.pm/mix/#{canonical}" ], groups_for_extras: [ + "Getting started": ~r"pages/getting-started/.*\.md$", "Anti-patterns": ~r"pages/anti-patterns/.*\.md$", References: ~r"pages/references/.*\.md$", "Meta-programming": ~r"pages/meta-programming/.*\.md$" @@ -43,7 +47,7 @@ canonical = System.fetch_env!("CANONICAL") groups_for_modules: [ # [Kernel, Kernel.SpecialForms], - "Basic Types": [ + "Data Types": [ Atom, Base, Bitwise,