Skip to content

Commit

Permalink
Adding slides for typespecs.
Browse files Browse the repository at this point in the history
  • Loading branch information
algogrit committed Mar 19, 2024
1 parent 3f40141 commit 2e45c08
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 0 deletions.
105 changes: 105 additions & 0 deletions docs/slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,111 @@ class: center, middle
---
class: center, middle

## Typespecs

---
class: center, middle

Elixir is a dynamically typed language, and as such, type specifications are never used by the compiler to optimize or modify code.

---

Still, using type specifications is useful because:

- they provide documentation (for example, tools such as ExDoc show type specifications in the documentation)

- they're used by tools such as Dialyzer, that can analyze code with typespecs to find type inconsistencies and possible bugs

---

Type specifications (most often referred to as typespecs) are defined in different contexts using the following attributes:

- `@type`
- `@opaque`
- `@typep`
- `@spec`
- `@callback`
- `@macrocallback`

---
class: center, middle

```elixir
defmodule StringHelpers do
@typedoc "A word from the dictionary"
@type word() :: String.t()

@spec long_word?(word()) :: boolean()
def long_word?(word) when is_binary(word) do
String.length(word) > 8
end
end
```

---

- We declare a new type (`word()`) that is equivalent to the string type (`String.t()`).

- We describe the type using a `@typedoc`, which will be included in the generated documentation.

- We specify that the `long_word?/1` function takes an argument of type `word()` and returns a boolean (`boolean()`), that is, either `true` or `false`.

---
class: center, middle

### [Types and their syntax](https://hexdocs.pm/elixir/typespecs.html#types-and-their-syntax)

---

#### Remote Types

Any module is also able to define its own types and the modules in Elixir are no exception. For example, the Range module defines a `t/0` type that represents a range: this type can be referred to as `Range.t/0`. In a similar fashion, a string is `String.t/0`, and so on.

---
class: center, middle

### Behaviours

---
class: center, middle

Behaviours in Elixir (and Erlang) are a way to separate and abstract the generic part of a component (which becomes the behaviour module) from the specific part (which becomes the callback module).

---
class: center, middle

#### Parser Example

---

Defining a callback is a matter of defining a specification for that callback, made of:

- the callback name (`parse` or `extensions` in the example)

- the arguments that the callback must accept (`String.t`)

- the expected type of the callback return value

---
class: center, middle

#### Using Behaviour

---
class: center, middle

Behaviours are useful because you can pass modules around as arguments and you can then call back to any of the functions specified in the behaviour.

---
class: center, middle

#### Optional Callbacks

Optional callbacks can be defined through the `@optional_callbacks` module attribute, which has to be a keyword list with `function` or `macro` name as key and `arity` as value.

---
class: center, middle

Code
https://github.com/AgarwalConsulting/elixir_training

Expand Down
9 changes: 9 additions & 0 deletions examples/12_typespecs/bad_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule BADParser do
@behaviour Parser

@impl Parser
def parse, do: {:ok, "something bad"}

@impl Parser
def extensions, do: ["bad"]
end
9 changes: 9 additions & 0 deletions examples/12_typespecs/csv_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule CSVParser do
@behaviour Parser

@impl Parser
def parse(str), do: {:ok, "some csv " <> str} # ... parse CSV

@impl Parser
def extensions, do: [".csv"]
end
26 changes: 26 additions & 0 deletions examples/12_typespecs/example_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule ExampleParser do
@spec parse_path(Path.t(), [module()]) :: {:ok, term} | {:error, atom}
def parse_path(filename, parsers) do
with {:ok, ext} <- parse_extension(filename),
{:ok, parser} <- find_parser(ext, parsers),
{:ok, contents} <- File.read(filename) do
parser.parse(contents)
end
end

defp parse_extension(filename) do
if ext = Path.extname(filename) do
{:ok, ext}
else
{:error, :no_extension}
end
end

defp find_parser(ext, parsers) do
if parser = Enum.find(parsers, fn parser -> ext in parser.extensions() end) do
{:ok, parser}
else
{:error, :no_matching_parser}
end
end
end
9 changes: 9 additions & 0 deletions examples/12_typespecs/json_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule JSONParser do
@behaviour Parser

@impl Parser
def parse(str), do: {:ok, "some json " <> str} # ... parse JSON

@impl Parser
def extensions, do: [".json"]
end
11 changes: 11 additions & 0 deletions examples/12_typespecs/parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Parser do
@doc """
Parses a string.
"""
@callback parse(String.t) :: {:ok, term} | {:error, atom}

@doc """
Lists all supported file extensions.
"""
@callback extensions() :: [String.t]
end
9 changes: 9 additions & 0 deletions examples/12_typespecs/simple.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule StringHelpers do
@typedoc "A word from the dictionary"
@type word() :: String.t()

@spec long_word?(word()) :: boolean()
def long_word?(word) when is_binary(word) do
String.length(word) > 8
end
end

0 comments on commit 2e45c08

Please sign in to comment.