A Talk given at NYCElixir Feb 21, 2017 by Dan Janowski. An introduction to types in Elixir, success typing, and examples of how to write typespecs as well as including dialyzer in your app and some details on how to interpret the output.
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
Elixir and Dialyzer, Types and Typespecs, using and understanding them
1. ELIXIR’S TYPE OF OPTIMISM
HOW YOU CAN BE A TYPE OPTIMIST WITH
DIALYZER AND TYPESPEC
2. INTRODUCTION
TYPE BASICS
▸ Fundamental types: integer, atom,
list, tuple, function
▸ Composite types: list of integers,
tuple with varied members
▸ System of types: use of fundamental
and composite types in functions,
variables and structs.
3. INTRODUCTION
ONE WAY TO THINK ABOUT A TYPE SYSTEM
▸ The function call structure is the wiring
▸ The types define the expected signal shapes going in and
out.
A type is a pattern, it defines
structure and can define legal
value ranges or enumerations.
4. INTRODUCTION
TYPE CHECKING
▸ Static type checking
▸ Enforced during compilation
▸ Everything has to be right, declared
▸ Dynamic type checking
▸ Defined by use
▸ Runtime evaluation of operations at the fundamental level
▸ Pattern matching
6. ELIXIR TYPESPEC
THE ELIXIR POSITION
▸ Dynamic typing
▸ Runtime enforcement of operations (“5” + 7)
▸ Pattern matching, guards on functions create some limits
▸ Testing can validate the intended use of a function
7. WHAT ABOUT TESTING
▸ Can test expected and unexpected inputs
▸ Exercise the code to validate type
handling
▸ Developers are optimistic testers
▸ Easy to have unexpected input conditions
▸ Doesn’t protect future code.
▸ Best at validating a function’s
transformation
ELIXIR TYPESPEC
8. ELIXIR TYPESPEC
THE SUPER POWER OF DIALYZER
▸ How is a function called by actual code?
▸ How does a function call other functions?
▸ Write signatures that are declarative
▸ Infer types up and down the call stack
▸ More exhaustive than what can be written as tests
▸ Your functions and structs are documented and validated
9. ELIXIR TYPESPEC
DOWNSIDE
▸ Not DRY
▸ Define a struct
▸ Define a @type
▸ Write a function
▸ Write a @spec
▸ dialyzer takes a little time to run, output can be cryptic
10. DIALYZER
DIALYZER READS ALL THE TYPES DECLARED
▸ Reads all the type specifications in the core modules,
dependencies
▸ This takes a while, but is cached, done once.
▸ aka PLT, persistent lookup table
▸ Reads all the types you’ve declared
12. SET UP
ADD DIALYXIR TO MIX.EXS DEPS()
defmodule Talk.Mixfile do
use Mix.Project
. . .
defp deps do
[
{:dialyxir, "~> 0.4", only: [:dev], runtime: false},
]
end
end
$ mix deps.get
$ mix deps.compile
Then
13. SET UP
NEW MIX TASK
$ mix helpmix # Runs the default task (current: "mix run")
mix app.start # Starts all registered apps
mix app.tree # Prints the application tree
mix archive # Lists installed archives
mix archive.build # Archives this project into a .ez file
mix archive.install # Installs an archive locally
mix archive.uninstall # Uninstalls archives
mix clean # Deletes generated application files
mix cmd # Executes the given command
mix compile # Compiles source files
mix deps # Lists dependencies and their status
mix deps.clean # Deletes the given dependencies' files
mix deps.compile # Compiles dependencies
mix deps.get # Gets all out of date dependencies
mix deps.tree # Prints the dependency tree
mix deps.unlock # Unlocks the given dependencies
mix deps.update # Updates the given dependencies
mix dialyzer # Runs dialyzer with default or project-defined flags.
mix do # Executes the tasks separated by comma
mix escript # Lists installed escripts
mix escript.build # Builds an escript for the project
mix escript.install # Installs an escript locally
mix escript.uninstall # Uninstalls escripts
mix help # Prints help information for tasks
mix hex # Prints Hex help information
mix hex.build # Builds a new package version locally
mix hex.config # Reads, updates or deletes Hex config
mix hex.docs # Fetch or open documentation of a package
mix hex.info # Prints Hex information
mix hex.key # Manages Hex API key
mix hex.outdated # Shows outdated Hex deps for the current project
mix hex.owner # Manages Hex package ownership
mix hex.public_keys # Manages Hex public keys
mix hex.publish # Publishes a new package version
mix hex.retire # Retires a package version
mix hex.search # Searches for package names
mix hex.user # Registers or manages Hex user
mix loadconfig # Loads and persists the given configuration
mix local # Lists local tasks
mix local.hex # Installs Hex locally
mix local.phoenix # Updates Phoenix locally
mix local.public_keys # Manages public keys
mix local.rebar # Installs Rebar locally
mix new # Creates a new Elixir project
mix phoenix.new # Creates a new Phoenix v1.2.1 application
mix profile.fprof # Profiles the given file or expression with fprof
mix run # Runs the given file or expression
mix test # Runs a project's tests
mix xref # Performs cross reference checks
iex -S mix # Starts IEx and runs the default task
14. SET UP
FIRST RUN
flash:talk 03:32 525$ mix dialyzerChecking PLT...
[:compiler, :elixir, :kernel, :logger, :stdlib]
Finding suitable PLTs
Looking up modules in dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Looking up modules in dialyxir_erlang-19.2_elixir-1.4.1.plt
Looking up modules in dialyxir_erlang-19.2.plt
Finding applications for dialyxir_erlang-19.2.plt
Finding modules for dialyxir_erlang-19.2.plt
Creating dialyxir_erlang-19.2.plt
Looking up modules in dialyxir_erlang-19.2.plt
Removing 3 modules from dialyxir_erlang-19.2.plt
Checking 11 modules in dialyxir_erlang-19.2.plt
Adding 149 modules to dialyxir_erlang-19.2.plt
Finding applications for dialyxir_erlang-19.2_elixir-1.4.1.plt
Finding modules for dialyxir_erlang-19.2_elixir-1.4.1.plt
Copying dialyxir_erlang-19.2.plt to dialyxir_erlang-19.2_elixir-1.4.1.plt
Looking up modules in dialyxir_erlang-19.2_elixir-1.4.1.plt
Checking 160 modules in dialyxir_erlang-19.2_elixir-1.4.1.plt
Adding 220 modules to dialyxir_erlang-19.2_elixir-1.4.1.plt
Finding applications for dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Finding modules for dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Copying dialyxir_erlang-19.2_elixir-1.4.1.plt to dialyxir_erlang-19.2_elixir-1.
4.1_deps-dev.plt
Looking up modules in dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Checking 380 modules in dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Adding 57 modules to dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt
Starting Dialyzer
dialyzer --no_check_plt --fullpath --plt /Users/danj/Documents/elixir/typespec-
talk/talk/_build/dev/dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt /Users/danj
/Documents/elixir/typespec-talk/talk/_build/dev/lib/talk/ebin
Proceeding with analysis... done in 0m1.38s
done (passed successfully)
flash:talk 03:38 526$
‣ mix dialyzer
‣ First run on an empty
project ~6min
15. CARDS
CARD EXAMPLE
defmodule Card do
def kind({_suit,value}) when is_number(value), do: :number
def kind({_suit,_value}), do: :face
def check_cards(card) do
IO.puts("kind(#{inspect(card)}) -> #{inspect(kind(card))}")
end
def main do
check_cards({:spades, :king})
check_cards({:rubies, 10})
end
end
16. CARDS
DIALYZER NORMAL RESULT
$ mix dialyzer
Checking PLT...
[:compiler, :elixir, :kernel, :logger, :stdlib]
PLT is up to date!
Starting Dialyzer
dialyzer --no_check_plt --fullpath --plt /Users/danj/Documents/elixir/typespec-
talk/talk/_build/dev/dialyxir_erlang-19.2_elixir-1.4.1_deps-dev.plt /Users/
danj/Documents/elixir/typespec-talk/talk/_build/dev/lib/talk/ebin
Proceeding with analysis... done in 0m1.61s
done (passed successfully)
17. CARDS
ADD TYPES AND A SPEC
1defmodule Card do
2
7
11
12 def check_cards(card) do
13 IO.puts("kind(#{inspect(card)}) -> #{inspect(kind(card))}")
14 end
15
16 def main do
17 check_cards({:spades, :king})
18 check_cards({:rubies, 10})
19 end
20end
3 @type suit :: ( :spade | :heart | :club | :diamond )
4 @type value :: ( 2..10 | :jack | :queen | :king | :ace )
5 @type card :: { suit, value }
6 @type card_kind :: { :number | :face }
8 @spec kind(card()) :: card_kind()
9 def kind({_suit,value}) when is_number(value), do: :number
10 def kind({_suit,_value}), do: :face
18. CARDS
Proceeding with analysis...
lib/card.ex:8: Invalid type specification for function
'Elixir.Card':kind/1. The success typing is ({_,_}) -> 'face' |
'number'
done in 0m1.72s
done (warnings were emitted)
8 @spec kind(card()) :: card_kind()
5 @type card :: { suit, value }
6 @type card_kind :: { :number | :face }
6 @type card_kind :: ( :number | :face )
oops
22. CARDS
CARD EXAMPLE
defmodule Card do
def kind({_suit,value}) when is_number(value), do: :number
def kind({_suit,_value}), do: :face
def check_cards(card) do
IO.puts("kind(#{inspect(card)}) -> #{inspect(kind(card))}")
end
def main do
check_cards({:spades, :king})
check_cards({:rubies, 10})
end
end