|
1 | 1 | --- |
2 | 2 | layout: getting_started |
3 | | -title: 20 Where to go next |
| 3 | +title: 20 Typespecs and behaviours |
4 | 4 | guide: 20 |
5 | | -last: true |
6 | 5 | --- |
7 | 6 |
|
8 | 7 | # {{ page.title }} |
9 | 8 |
|
10 | 9 | {% include toc.html %} |
11 | 10 |
|
12 | | -Eager to learn more? Keep on reading! |
| 11 | +## 20.1 Types and specs |
13 | 12 |
|
14 | | -## 20.1 Build your first Elixir project |
| 13 | +Elixir is a dynamically typed language, so all types in Elixir are inferred by the runtime. Nonetheless, Elixir comes with **typespecs**, which are a notation used for: |
15 | 14 |
|
16 | | -In order to get your first project started, Elixir ships with a build tool called Mix. You can get your new project started by simply running: |
| 15 | +1. declaring custom data types; |
| 16 | +2. declaring typed function signatures (specifications). |
17 | 17 |
|
18 | | - mix new path/to/new/project |
| 18 | +### 20.1.1 Function specifications |
19 | 19 |
|
20 | | -We have written a guide that covers how to build an Elixir application, with its own supervision tree, configuration, tests and more. The application works as a distributed key-value store where we organize key-value pairs into buckets and distribute those buckets across multiple nodes: |
| 20 | +By default, Elixir provides some basic types, such as `integer` or `pid`, as well as more complex types: for example, the `round/1` function, which rounds a float to its nearest integer, takes a `number` as an argument (an `integer` or a `float`) and returns a `float`. As you can see [in its documentation](http://elixir-lang.org/docs/stable/elixir/Kernel.html#round/1), `round/1`'s typed signature is written as: |
21 | 21 |
|
22 | | -* [Mix and OTP](/getting_started/mix_otp/1.html) |
| 22 | +``` |
| 23 | +round(number) :: integer |
| 24 | +``` |
23 | 25 |
|
24 | | -## 20.2 Community and other resources |
| 26 | +`::` means that the function on the left side *returns* a value whose type is what's on the right side. Function specs are written with the `@spec` directive, placed right before the function definition. The `round/1` function could be written as: |
25 | 27 |
|
26 | | -On the sidebar, you can find the link to some Elixir books and screencasts. There are plenty of Elixir resources out there, like conference talks, open source projects, and other learning materials produced by the community. |
| 28 | +```elixir |
| 29 | +@spec round(number) :: integer |
| 30 | +def round(number), do: # implementation... |
| 31 | +``` |
27 | 32 |
|
28 | | -Remember that in case of any difficulties, you can always visit the **#elixir-lang** channel on **irc.freenode.net** or send a message to the [mailing list](http://groups.google.com/group/elixir-lang-talk). You can be sure that there will be someone willing to help. And to keep posted on the latest news and announcements, follow the [blog](/blog/) and follow language developments on the [elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). |
| 33 | +Elixir supports compound types as well. For example, a list of integers has type `[integer]`. You can see all the types provided by Elixir [in the typespecs docs](http://elixir-lang.org/docs/stable/elixir/Kernel.Typespec.html). |
29 | 34 |
|
30 | | -Don't forget [you can also check the source code of Elixir itself](https://github.com/elixir-lang/elixir), which is mostly written in Elixir (mainly the `lib` directory), or [explore Elixir's documentation](/docs.html). |
| 35 | +### 20.1.2 Defining custom types |
31 | 36 |
|
32 | | -## 20.3 A Byte of Erlang |
| 37 | +While Elixir provides a lot of useful built-in types, it's convenient to define custom types when appropriate. This can be done when defining modules modules through the `@type` directive. |
33 | 38 |
|
34 | | -As the main page of this site puts it: |
| 39 | +Say we have a `RudeCalculator` module, which performs the usual arithmetic operations (sum, product and so on) but, instead of returning numbers, it returns tuples with the result of an operation as the first element and a random offense as the second element. |
35 | 40 |
|
36 | | -> Elixir is a programming language built on top of the Erlang VM. |
| 41 | +```elixir |
| 42 | +defmodule LousyCalculator do |
| 43 | + @spec add(number, number) :: {number, String.t} |
| 44 | + def add(x, y), do: {x + y, "You need a calculator to do that?!"} |
37 | 45 |
|
38 | | -Sooner or later, an Elixir developer will want to interface with existing Erlang libraries. Here's a list of online resources that cover Erlang's fundamentals and its more advanced features: |
| 46 | + @spec multiply(number, number) :: {number, String.t} |
| 47 | + def multiply(x, y), do: {x * y, "Jeez, come on!"} |
| 48 | +end |
| 49 | +``` |
39 | 50 |
|
40 | | -* This [Erlang Syntax: A Crash Course](/crash-course.html) provides a concise intro to Erlang's syntax. Each code snippet is accompanied by equivalent code in Elixir. This is an opportunity for you to not only get some exposure to Erlang's syntax but also review some of the things you have learned in this guide. |
| 51 | +As you can see in the example, tuples are a compound type and each tuple is identified by the types inside it. To understand why `String.t` is not written as `string`, have another look at the [typespecs docs](http://elixir-lang.org/docs/stable/elixir/Kernel.Typespec.html). |
41 | 52 |
|
42 | | -* Erlang's official website has a short [tutorial](http://www.erlang.org/course/concurrent_programming.html) with pictures that briefly describe Erlang's primitives for concurrent programming. |
| 53 | +Defining function specs this way works, but it quickly becomes annoying since we're repeating the type `{number, String.t}` over and over. We can use the `@type` directory in order to declare our own custom type. |
43 | 54 |
|
44 | | -* [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/) is an excellent introduction to Erlang, its design principles, standard library, best practices and much more. If you are serious about Elixir, you'll want to get a solid understanding of Erlang principles. Once you have read through the crash course mentioned above, you'll be able to safely skip the first couple of chapters in the book that mostly deal with the syntax. When you reach [The Hitchhiker's Guide to Concurrency](http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency) chapter, that's where the real fun starts. |
| 55 | +```elixir |
| 56 | +defmodule LousyCalculator do |
| 57 | + @typedoc """ |
| 58 | + Just a number followed by a string. |
| 59 | + """ |
| 60 | + @type number_with_offense :: {number, String.t} |
| 61 | + |
| 62 | + @spec add(number, number) :: number_with_offense |
| 63 | + def add(x, y), do: {x + y, "You need a calculator to do that?!"} |
| 64 | + |
| 65 | + @spec multiply(number, number) :: number_with_offense |
| 66 | + def multiply(x, y), do: {x * y, "Jeez, come on!"} |
| 67 | +end |
| 68 | +``` |
| 69 | + |
| 70 | +The `@typedoc` directive, similarly to the `@doc` and `@moduledoc` directives, is used to document custom types. |
| 71 | + |
| 72 | +Custom types defined through `@type` are exported and available outside the module they're defined in: |
| 73 | + |
| 74 | +```elixir |
| 75 | +defmodule PoliteCalculator do |
| 76 | + @spec add(number, number) :: number |
| 77 | + def add(x, y), do: make_polite(LousyCalculator.add(x, y)) |
| 78 | + |
| 79 | + @spec make_polite(LousyCalculator.number_with_offense) :: number |
| 80 | + defp make_polite({num, _offense}), do: num |
| 81 | +end |
| 82 | +``` |
| 83 | + |
| 84 | +If you want to keep a custom type private, you can use the `@typep` directive instead of `@type`. |
| 85 | + |
| 86 | +### 20.1.3 Static code analysis |
| 87 | + |
| 88 | +Typespecs are not only useful to developers and as additional documentation. The Erlang tool [Dyalizer](http://www.erlang.org/doc/man/dialyzer.html), for example, uses typespecs in order to perform static analysis of code. That's why, in the `PoliteCalculator` example, we wrote a spec for the `make_polite/1` function even if it was defined as a private function. |
| 89 | + |
| 90 | + |
| 91 | +## 20.2 Behaviours |
| 92 | + |
| 93 | +Many modules share the same public API. Take a look at [Plug](https://github.com/elixir-lang/plug), which, as it description states, is a **specification** for composable modules in web applications. Each *plug* is a module which **has to** implement at least two public functions: `init/1` and `call/2`. |
| 94 | + |
| 95 | +Behaviors provide a way to: |
| 96 | + |
| 97 | +* define a set of functions that have to be implemented by a module; |
| 98 | +* ensure that a module implements all the functions in that set. |
| 99 | + |
| 100 | +If you have to, you can think of behaviours like interfaces in object oriented languages like Java: a set of function signatures that a module has to implement. |
| 101 | + |
| 102 | +### 20.2.1 Defining behaviours |
| 103 | + |
| 104 | +Say we have want to implement a bunch of parsers, each parsing structured data: for example, a JSON parser and a YAML parser. Each of these two parsers will *behave* the same way: both will provide a `parse/1` function and a `extensions/0` function. The `parse/1` function will return an Elixir representation of the structured data, while the `extensions/0` function will return a list of file extensions that can be used for each type of data (e.g., `.json` for JSON files). |
| 105 | + |
| 106 | +We can create a `Parser` behaviour: |
| 107 | + |
| 108 | +```elixir |
| 109 | +defmodule Parser do |
| 110 | + use Behaviour |
| 111 | + |
| 112 | + defcallback parse(String.t) :: any |
| 113 | + defcallback extensions() :: [String.t] |
| 114 | +end |
| 115 | +``` |
| 116 | + |
| 117 | +Modules adopting the `Parser` behaviour will have to implement all the functions defined with `defcallback`. As you can see, `defcallback` expects a function name but also a function specification like the ones used with the `@spec` directive we saw above. |
| 118 | + |
| 119 | +### 20.2.2 Adopting behaviours |
| 120 | + |
| 121 | +Adopting a behaviour is straightforward: |
| 122 | + |
| 123 | +```elixir |
| 124 | +defmodule JSONParser do |
| 125 | + @behaviour Parser |
| 126 | + |
| 127 | + def parse(str), do: # ... parse JSON |
| 128 | + def extensions, do: ["json"] |
| 129 | +end |
| 130 | +``` |
| 131 | + |
| 132 | +```elixir |
| 133 | +defmodule YAMLParser do |
| 134 | + @behaviour Parser |
| 135 | + |
| 136 | + def parse(str), do: # ... parse YAML |
| 137 | + def extensions, do: ["yml"] |
| 138 | +end |
| 139 | +``` |
| 140 | + |
| 141 | +If a module adopting a given behaviour doesn't implement one of the callbacks required by that behaviour, a compile-time warning will be generated. |
0 commit comments