Sgoettschkes
Typespec and opts
Hi,
I’m trying to find examples of typespec usage with optional arguments. I assume the following is one way to write a spec with optional arguments:
@spec example1(String.t(), option1: String.t(), option2: integer()) :: String.t()
def example1(str, opts \\ [])
If I’d like to extract the options into a type because it is used in other places as well, would this be the correct way:
@type opts :: [option1: String.t(), option2: integer()]
@spec example1(String.t(), opts | nil) :: String.t()
def example1(str, opts \\ [])
Do I even need the | nil part?
Thanks,
Sebastian
First Post!
NobbZ
Both of your approaches require the elements of the option list to appear in the correct order, and also that all of them appear.
You probably want this:
@type opts :: {:option1, String.t()} | {:option2, integer()}
@spec example(String.t(), [opts]) :: String.t()
Most Liked
LostKobrakai
Typespecs don’t have optional arguments. If you want to supply a typespec for both generated arities you need two specs:
@spec example1(String.t()) :: String.t()
@spec example1(String.t(), opts) :: String.t()
def example1(str, opts \\ []), do: …
Optional arguments do not mean they become nil when absent. Your function will become the following:
def example1(str), do: example1(str, [])
def example1(str, opts), do: …
There’s no implicit data being injected anywhere.
ityonemo
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 (Typespecs reference — Elixir v1.20.2, 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.
NobbZ
You are indeed correct, my “order” argument isn’t valid. I got confused with pattern matching on keyword lists.
Last Post!
Sgoettschkes
Thanks for your comments. I decided to use the spec suggested by LostKobrakai:
@spec example1(String.t()) :: String.t()
@spec example1(String.t(), opts) :: String.t()
def example1(str, opts \\ []), do: …
I do think this makes it very clear how to call the function.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









