James_E
Collapsing/compressing redundant typespecs?
I’m writing a multiset library. It’s got 2 modules: one which works on raw “multisets” (which are maps of elements to quantities), and another which is just a Struct type wrapping the same functionality in much nicer ergonomics.
For the former module, all the methods support so-called “lax” multisets, where some element => 0 records may be included; but many methods include a strict \\ :lax :: :lax | :strict parameter that can be used to switch on better codepaths whenever the caller can guarantee the multiset isn’t “lax”.
I’m trying to write the type system to support that, so that Dialyzer will be helpful to any consumers. However, the type signature for one method, delete, has turned out suspiciously verbose; I’m wondering if there’s any way to shore this up with some syntax that more clearly semantically indicates “this type in the output is the same as this type in the input, and said type must in any case fit some constraint, and you should try fitting the allowed types in this particular priority order”, without this combinatorial explosion:
@type t(value) :: %{optional(value) => pos_integer()}
@type t() :: t(term)
@type t_lax(value) :: %{optional(value) => non_neg_integer()}
@type t_lax() :: t_lax(term)
@type t0(value) :: Enumerable.t({value, non_neg_integer()})
@type t0() :: t0(term)
# …
@spec delete(t(e), term) :: t(e) when e: term # non-lax output is guaranteed from non-lax input
@spec delete(t_lax(e), term) :: t_lax(e) when e: term
@spec delete(t(e), term, :all) :: t(e) when e: term # non-lax output is guaranteed from non-lax input
@spec delete(t_lax(e), term, :all) :: t_lax(e) when e: term
@spec delete(t(e), term, non_neg_integer()) :: t(e) when e: term # non-lax output is guaranteed from non-lax input
@spec delete(t_lax(e), term, non_neg_integer()) :: t_lax(e) when e: term
def delete(ms, element, count)
def delete(ms, element, :all), do: Map.delete(ms, element)
def delete(ms, element, count) when is_pos_integer(count) do
case ms do
%{^element => n1} ->
n2 = n1 - count
if n2 > 0 do
%{ms | element => n2}
else
Map.delete(ms, element)
end
%{} ->
ms
end
end
def delete(ms, _, 0), do: ms
Most Liked
LostKobrakai
@spec delete(mset, term) :: mset when mset: t(term) | t_lax(term)
The specs you wrote would be merged to the following anyways:
@spec delete(t(e) | t_lax(e), term) :: t(e) | t_lax(e) when e: term
LostKobrakai
No. Multiple specs for a single function(/arity) will be merged where all the individual parameters and the return type are put into union. Only if you do the @spec fun(x) :: x when x: term notation do you “hint” – not sure how hard a hint – that input and output are of the same type
LostKobrakai
The hexdocs page describes the typespec syntax, not dialyzer – the type checker using the specs. These are really separate things. But I’m not aware of a comprehensive suite of docs around how dialyzer works. I essentially treat is as best effort checking, because really that’s what it does. E.g. it will switch out granular types, when getting bigger in complexity, for less granular types when checking. In the end the only guarantee dialyzer makes is that there is an issue (even if just with the typespecs) if it reports an issue. Exhaustiveness in finding issues was never a goal of dialyzer.
As for your example I don’t think there is a better way to write this given the distinct type parameters used between input and return value.
Last Post!
al2o3cr
FWIW, distinguishing situations like:
@spec function(integer) :: atom
@spec function(atom) :: integer
and:
@spec function(integer) :: integer
@spec function(atom) :: atom
(both of which Dialyzer collapses to @spec function(integer | atom) :: integer | atom) is a key motivation for the set-theoretic type work:
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









