Regime 1:
If there is any path through your code that does not match your
specified types, then dialyzer will report an error.
Regime 2:
If there is any path through your code that does match your
specified types, then dialyzer will not report an error.
diaylyzer operates under Regime 2. In your case, if you call foo(hello)
:
1> c(foobar).
{ok,foobar}
2> foobar:foo(hello).
ok
3>
...then foo()
was called with the required argument type atom(), and foo()
returned one of the required types, ok
, so dialyzer doesn't report an error.
Remember, Dialyzer is optimistic. It has figurative faith in your
code, and because there is the possibility that the function call to
[foo] succeeds ..., Dialyzer will keep silent. No type
error is reported in this case.
http://learnyousomeerlang.com/dialyzer
Dialyzer can be more confounding than in your example, for instance:
-module(my).
-export([test/0, myand/2]).
%-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
test() ->
myand({a,b}, [1,2]).
myand(true, true) -> true;
myand(false, _) -> false;
myand(_, false) -> false.
- Can you spot the error in the code?
- Will dialyzer find the error? Take a minute and try to determine what you can infer about the types of the arguments to myand().
Answer: The first argument to myand()
has to be a boolean()...well that's not actually true--look at the last clause of myand(). The first argument can also be anything. The three function clauses tell us that all the possible values for the first argument are: true, false, or anything. A type that encompasses all three possibilities is any(). Then dialyzer looks at the second argument, and dialyzer comes to the same conclusion about the second argument's type. Therefore, dialyzer infers the type of myand()
to be:
myand(any(), any()) -> boolean().
...which means that in dialyzer's opinion calling myand({a,b}, [1,2])
isn't an error. Huh?? Au contraire, my feathered friend:
1> c(my).
{ok,my}
2> my:test().
** exception error: no function clause matching my:myand({a,b},[1,2]) (my.erl, line 9)
3>
Obviously, the intent of the myand()
code is that myand()
should require at least one boolean() argument--but apparently dialyzer collects information on each variable in isolation:
+---------------------------------------+
| 1st arg info |
| |
| info1 true |
| info2 false |
| info3 any |
| --------- |
| any() -- inferred type |
| |
+---------------------------------------+
+---------------------------------------+
| 2nd arg info |
| |
| info1 true |
| info2 any |
| info3 false |
| ------- |
| any() -- inferred type |
| |
+---------------------------------------+
As a consequence, the test()
/myand()
code is a case where dialyzer isn't able to report an actual error in your code.
There are ways to help dialyzer find errors:
1) Enumerate all possible arguments in the function clauses:
myand(true, true) -> true;
myand(false, true) -> false;
myand(true, false) -> false.
"Programming Erlang" p. 152 warns against using _
for arguments if you are using dialyzer.
2) Or, if there are too many cases to enumerate, you can use guards to specify argument types:
myand(true, true) -> true;
myand(false, _Y) when is_boolean(_Y) -> false;
myand(_X, false) when is_boolean(_X) -> false.
3) And of course, you can employ a type specification:
-spec myand(boolean(), boolean()) -> boolean().