Are you sure that all elements are required in that order in my first example? At least dialyzer is not picking this up with a straight up call to example1 with only a string passed. From what I understand this should be picked up?
I am pretty sure that is not the case. There is no list type in the dialyzer typesystem that remembers the order of the internal types (or any requirement for an internal type to exist besides the general “nonempty”), and Elixir has special cased keyword lists in typespecs (https://hexdocs.pm/elixir/typespecs.html#literals, section “keyword list”), even though it looks a bit weird because no other list types have commas in them. Also it’s a bit strange that the documentation doesn’t mention that you can use more than one k/v types in the kwl special syntax, but you can… Maybe I should PR a fix to that doc.
Both of @Sgoettschkes examples are syntactically correct. 1 and 2 without the nil are semantically correct for what he’s trying to do. I would generally disfavor spec #1 because one doesn’t expect to unroll Elixir’s synatactic sugar inside of a typespec. It looks confusing and could be mistaken for an arity-3 function. #2 (without the nil) is very much idiomatic elixir; nobody really types out the version of the function with the default value, and I think that the dialyzer system is smart enough to deal with it correctly if you omit the lower-arity functions.
@NobbZ’s example is closest to what you would idiomatically see in erlang typespecs, and it’s the most flexible, since you can later do type magic and pull apart groups of options, for the purposes of documenting them separately, or applying them in separate cases if some functions take only a subset of the options.