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 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
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
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
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 |
| {'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 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
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

Elixir and Dialyzer, Types and Typespecs, using and understanding them

  • 1.
    ELIXIR’S TYPE OFOPTIMISM HOW YOU CAN BE A TYPE OPTIMIST WITH DIALYZER AND TYPESPEC
  • 2.
    INTRODUCTION TYPE BASICS ▸ Fundamentaltypes: 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 TOTHINK 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 ▸ Statictype 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
  • 5.
    OH BOY HOLY WAROF TYPE TYPES WHICH IS BETTER?
  • 6.
    ELIXIR TYPESPEC THE ELIXIRPOSITION ▸ 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 SUPERPOWER 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 ▸ NotDRY ▸ 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 ALLTHE 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
  • 11.
  • 12.
    SET UP ADD DIALYXIRTO 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 MIXTASK $ 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:talk03: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 Carddo 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 ANDA 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
  • 19.
    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)
  • 20.
    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 | | {'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 )
  • 21.
    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
  • 22.
    CARDS CARD EXAMPLE defmodule Carddo 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
  • 23.
    STRUCT TYPES AND STRUCT 1defmoduleCard 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
  • 24.
  • 25.
    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
  • 26.