Elixir and Dialyzer, Types and Typespecs, using and understanding them
-
Upload
dan-janowski -
Category
Software
-
view
62 -
download
2
Transcript of Elixir and Dialyzer, Types and Typespecs, using and understanding them
ELIXIR’S TYPE OF OPTIMISM
HOW YOU CAN BE A TYPE OPTIMIST WITH DIALYZER AND TYPESPEC
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.
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.
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
OH BOY
HOLY WAR OF TYPE TYPES
WHICH IS BETTER?
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
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
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
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
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
LET’S DO IT
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
SET UP
NEW MIX TASK
$ mix help mix # 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
SET UP
FIRST RUN
flash:talk 03:32 525$ mix dialyzer Checking 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
CARDS
CARD EXAMPLEdefmodule 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
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)
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
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
CARDS
Proceeding with analysis... lib/card.ex:16: Function main/0 has no local return lib/card.ex:17: The call 'Elixir.Card':check_cards({'spades','king'}) will never return since it differs in the 1st argument from the success typing arguments: ({'club','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'diamond','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'heart','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'spade','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}) lib/card.ex:18: The call 'Elixir.Card':check_cards({'rubies',10}) will never return since it differs in the 1st argument from the success typing arguments: ({'club','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'diamond','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'heart','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'spade','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}) done in 0m1.75s done (warnings were emitted)
CARDS
17 check_cards({:spades, :king})
lib/card.ex:17: The call 'Elixir.Card':check_cards({'spades','king'})
will never return since it differs in the 1st argument from the success typing arguments:
({'club','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'diamond','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'heart','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} | {'spade','ace' | 'jack' | 'king' | 'queen' | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} )
CARDS
1defmodule Card do 2 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 ) 7 8 @spec kind(card()) :: card_kind() 9 def kind({_suit,value}) when is_number(value), do: :number 10 def kind({_suit,_value}), do: :face 11 12 @spec check_cards(card()) :: any() 13 def check_cards(card) do 14 IO.puts("kind(#{inspect(card)}) -> #{inspect(kind(card))}") 15 end 16 17 def main do 18 check_cards({:spade, :king}) 19 check_cards({:diamond, 10}) 20 end 21end
CARDS
CARD EXAMPLEdefmodule 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
STRUCT
TYPES AND STRUCT
1defmodule Card do 2 defstruct [:suit, :value] 3 4 @type suit :: ( :spade | :heart | :club | :diamond ) 5 @type value :: ( 2..10 | :jack | :queen | :king | :ace ) 6 7 @type t :: %Card{ suit: suit(), value: value()} 8 9 @type card_kind :: ( :number | :face ) 10 11 @spec kind(Card.t) :: card_kind() 12 def kind(%Card{value: value} = card) when is_number(value), do: :number 13 def kind(_card), do: :face 14end
ADDITIONAL RESOURCES
ADDITIONAL RESOURCES
DOCUMENTATION
▸ http://learnyousomeerlang.com/dialyzer
▸ https://hexdocs.pm/elixir/typespecs.html
▸ https://hex.pm/packages/dialyxir
▸ http://user.it.uu.se/~kostis/Papers/succ_types.pdf
▸ Elixir Conf 2016 Jason Voegele Dialyzer talk
THANK YOU