Can you define spec for function with specific arity?

I am working with behaviours and callbacks, so I am trying to define a spec for a function that should accept an argument which is also a function, but specifically a function that accepts 1 argument. I just discovered that there is a is_function that can be used in a guard clause:

I have something like this:

@callback something(fun :: function()) :: list()

and implementations look like this:

@impl true
def something(fun) do

It is important that the provided fun has an arity of 1. I could make a guard clause:

@impl true
def something(fun) when is_function(fun, 1) do

but I am wondering if it is possible to define this in the @callback spec?

1 Like

You can try:

@callback something(fun :: (term() -> term())) :: list()

Of course (term() -> term()), but it will not enforce it in any way.

1 Like

Yes, you can make a typespec for a function as well and then put it in the @callback definition. Or it can be put inline as @Marcus demonstrated.

You can read the Typespecs page, specifically the tables in when in doubt.

Ah, I did not realize that this is how you can annotate anonymous or captured functions:

                               ## (Anonymous) Functions
      | (-> type)                     # 0-arity, returns type
      | (type1, type2 -> type)        # 2-arity, returns type
      | (... -> type)                 # any arity, returns type

However, why won’t this be enforced as the other types are enforced?

It will be checked in most cases, not enforced. Nothing will prevent you from doing invalid call to such function.

As of now, the only type check that happens at compile time is determined by guards and pattern matching in function definitions (if I understand correctly how these new checks happen), not based on typespecs.

1 Like

generally typespecs are not enforced in the BEAM at all. The function/arity typespecs are kind of deficient and (IMO) incorrect as implemented in dialyzer currently, I made an opinion video about here:

1 Like

Well elixir is dynamically typed so the checks aren’t done at compile time as such but the compiler adds code which do the checks in the patterns and guards. The compiler quite happily ignores all your type specs though you can use the dialyzer tool which will try to check your code and warn you if it finds errors.