Type annotations / better type specs/ merging guards and type annotations

Hi, elixir folks! I want to propose a better syntax for type specs:

defmodule MyModule do
  use TDef

  tdef my_fun(x: integer, y: String) :: integer do
    1
  end
end

After compilation it should be equivalent to @spec definitions and so could be processed by dialyzer.

Also, one can use make type annotations compile to guards too

tdef f(l: list[integer]) do

to be compiled into something like

@spec f(l :: list(integer)
def f(l) where is_list(l) do ...

What do you think?

3 Likes

The proposed syntax clashes with pattern matching on keyword lists.

Or do you strictly disallow them?

How do we deal with multiple heads in your version?

2 Likes

Hi!

Definitely no need to disallow keywords) I think best way is to use syntax that doesn’t clash with anything:

tdef f(arg :: integer) do

Or, with a keyword

tdef f(keyword: arg :: integer) do

This approach doesn’t intend to change anything related to pattern matching and multiple function defs, so it is supposed to work as it does now.

What problem are you trying to solve?

I want to propose a better syntax for type specs

Personally I don’t see where it is better. I like when type annotations aren’t merged to declaration like it is in Erlang, Elixir, Elm and Haskell.
It seems like you’re trying to write Python (I see it in your profile) in Elixir.

1 Like

@fuelen The fact that you don’t like it is a valid opinion, and I can totally accept it as it is) The problem I’m solving is kind of obvious: sometimes you need to mention a variable in a function definition 3 times: in function signature, in the guard and in @spec. What I propose, though being a macros, carries little semantic payload as it intuitively clear what is the long way of writing this.

Imho there is nothing wrong with trying approaches from language X in language Y, is it?

It’s in progress. Been working on it as part of my “holiday coding sabbatical” that I do every year

(Check the tests in the “collections” branch for usage)

Almost done with basic types, still need to do some recursive type testing, user defined (not sure if trivially possible) and remote types. Still need to only inject checking in dev and test, and also still need to allow suppression of non-guard tests (it does deep testing on lists and maps) on a module or function by function basis

3 Likes

You do not need to mention variable name.

@spec foo(binary()) :: binary()
def foo(a), do: a

Is exactly the same as:

@spec foo(a :: binary()) :: binary()
def foo(a), do: a

However with Your proposal there is problem with multiple heads. How will you handle something like:

tdef foo(a :: binary()) when is_binary(a), do: a
tdef foo(a :: integer()) when is_integer(a), do: a + 1

Or similar situations.

1 Like

You can issue multiple @specs and it works fine. (I think)

Also I think in Elixir, mentioning variable name in the typespec is slightly preferred since it gives hints to ex_doc, but I’m not certain about that

I need to check this, but I also thought that different (or equal) @spec for different heads are ok.

How would you pattern match on the function head?

How would you add a guard to a function head?

How would you deal with structs? What about complex types?

  def my_fn(%{foo: bar :: integer}) :: integer do
    bar + 1
  end

If you’re curious about how to add guards to function heads, check out my code :wink:

https://github.com/ityonemo/typed_headers

Yay, I checked it out briefly, and I like it. I didn’t want to override [def: 2] because somebody else could also do this (for a little and harmless trick) in his function and it all will break. Also, probably, it makes sence to be more close to existing @spec syntax, and to write x :: integer() instead of x :: integer

Yes, as long as these aren’t overlapping, as then it will issue warning.

Yeah I should probably not rebind def. It was there for another elixirforum thread, and I didn’t bother changing it.

Implied parentheses work in existing @spec syntax, I prefer it as it’s cleaner, and anyways both styles work in typed_headers

You can expect my contribution to your code in near future :slight_smile:

Maybe this is what you are looking for.
https://github.com/skirino/croma

2 Likes

Funny. I have it implemented a while ago, but I even rejected to produce a package out of this, because it’s clearly an ugly anti-pattern.

2 Likes

How does it clash if there is brand new macro tdef introduced?

  1. It clashes with my expectations
  2. That macro does either not allow for pattern matching on keyword lists or needs to create a new syntax for it