Types that you wish were in Elixir

Do you ever wish Elixir had more types?
Share some of the ones that you frequently use.

If the outcome is good I will compile them into a library.

Here are some:

@typedoc """
A non-positive integer.

That is, any integer `<= 0`.
"""
@type non_pos_integer :: 0 | neg_integer()


@typedoc """
A keyword list with `key` specified.

For example: `keyword(version :: atom(), map())`
"""
@type keyword(key, value) :: list({key, value})


@typedoc """
A non-empty keyword list.
"""
@type nonempty_keyword(value) :: nonempty_list({atom(), value})


@typedoc """
A non-empty keyword list with `key` specified.

For example: `nonempty_keyword(version :: atom(), map())`
"""
@type nonempty_keyword(key, value) :: nonempty_list({key, value})
8 Likes

We can probably get syntactic sugar for string-keyed and atom-keyed maps? Technically these:

%{String.t() => term()} 
%{atom() => term()} 

…should be enough, but I do wonder whether having builtin syntax won’t help further adoption. Something like e.g. string_map() and atom_map().

Maybe some imitation of sum types as well? So instead of this:

@type pixel_value :: :red | :blue | :green

…we can have this:

@type pixel_value :: Enum(red | blue | green)

That would be just some syntactic sugar.

But I don’t think the community will agree on alternative syntaxes for things you already can express. I am mostly thinking out loud here.

1 Like

Hi @dimitrarvp, thanks for chiming in. Note that the intention of the post is not to make a proposal to the language. Just dream and list types that you find useful in your day-to-day development.

2 Likes

In that case I’ll stand by by my suggestion of having convenient and accepted aliases for string-keyed and atom-keyed maps. While you can easily express them as shown above, IMO it will pay off to have something more intuitively named.

1 Like

Yes, I was also writing some aliases for the empty types.
There is a divergence between pattern matching and type specs, for example, the type %{} … is that a synonym for map() or for an empty map?
some for <<>>, so i created

@type empty_bitstring :: <<>>

@type empty_binary :: <<>>

@type empty_map :: %{}
1 Like

This is my wishlist:

String literals:

@type my_string::"foo"

List literals:

@type my_list::[:foo, :bar]

Min-length tuples

@type my_tuple_at_least_two :: {any, any, ...}

5 Likes

:scream: I am surprised that literals are not accepted!

Well, I have put up this little library,

The usage is quite simple.

      defmodule Foo do
        use ExtendedTypes, all?: true

        @spec sample :: nonempty_keyword()
        def sample(), do: [a: 1]
      end

      defmodule Bar do
        use ExtendedTypes, only: [string_map: 0, atom_map: 0]

        @spec my_map(atom) :: atom_map()
        @spec my_map(String.t) :: string_map()
        def my_map(key) when is_atom(key), do: %{a: 1}
        def my_map(key) when is_binary(key), do: %{"a" => 1}
      end

If you run mix docs you can see all the supported types under the ExtededTypes.Types module.

Please let me know if you think it could be improved in any way.
Thank you.

2 Likes

Where would a type like this be useful?

Liveview handle_event callback is the first to come to mind

4 Likes

Anything json related too

1 Like

Lists are strictly [list_type | final_type] and there’s no additional granularity available.

There’s also some tricky “bugs”? in the spec, like nonempty improper lists are usually tagged as any for the final type, which technically includes the empty list, which is “not correct”

1 Like

That is precisely what I am trying to tackle with the library i just published,
Look at the types t:all/0 and t:all_but_empty_list/0 (commented out)
https://github.com/eksperimental/extended_types/blob/53a86539296796ad7843ba4f8de641b2b93ec52c/lib/extended_types/types.ex#L116-L159

I am thinking of creating a function/macro that can generate custom types where certain types are excluded from any().

Edit: It is covered in these lines:
@type any_but_list :: ...
https://github.com/eksperimental/extended_types/blob/07594b8acf45b78112e08ad56c6ad867e8f0b675/lib/extended_types/types.ex#L146-L156

@type improper_list :: nonempty_improper_list(any, any_but_list)
https://github.com/eksperimental/extended_types/blob/07594b8acf45b78112e08ad56c6ad867e8f0b675/lib/extended_types/types.ex#L192-L197

I think it will be a good addition to the Typespecs page, to list all these caveats

This may be relevant https://twitter.com/wojtekmach/status/1460631269688102920?s=21