Fl4m3Ph03n1x

Fl4m3Ph03n1x

Pattern matching functions and dialyzer

Background

I am trying to use dialyzer to validate type specs in my code, but I wonder what is the best way of using it when we have functions that use pattern matching.

Code

Let’s focus on this code segment from Designing Elixir Systems with OTP (type specs added by me):

# Validator.check returns :ok if the 1st parameter is true and returns the second otherwise.
defp validate_generator({name, generator}) when is_atom(name) and is_list(generator), do:
  Validator.check(generator != [], {:error, "can't be empty"})

defp validate_generator({name, generator}) when is_atom(name) and is_function(generator, 0), do:
  :ok

defp validate_generator(_generate), do:
  {:error, "must be a string to list or function pair"}

This code receives a tuple of an atom and a function and validates if the parameters have the correct format.

Dialyzing

There are 2 main ways of dialyzing this code:

@spec validate_generator(any) :: :ok | {:error, String.t}
defp validate_generator({name, generator}) when is_atom(name) and is_list(generator), do:
  Validator.check(generator != [], {:error, "can't be empty"})

defp validate_generator({name, generator}) when is_atom(name) and is_function(generator, 0), do:
  :ok

defp validate_generator(_generate), do:
  {:error, "must be a string to list or function pair"}

This is a generic approach, but I find it too generalist. Although it is true that validate_generator can receive anything as a parameter, if you actually pass it anything you will always end up in the 3rd pattern match, which is an error.

You also lack information about the other function clauses.

The other way of dialyzing this code would be the following:

@spec validate_generator({atom, list}) :: :ok | {:error, String.t}
defp validate_generator({name, generator}) when is_atom(name) and is_list(generator), do:
  Validator.check(generator != [], {:error, "can't be empty"})

@spec validate_generator(atom, function) :: :ok
defp validate_generator({name, generator}) when is_atom(name) and is_function(generator, 0), do:
  :ok

@spec validate_generator(any) :: {:error, "must be a string to list or function pair"}
defp validate_generator(_generate), do:
  {:error, "must be a string to list or function pair"}

This alternative has the advantage of making it clear the types of parameters expected for each pattern match clause, although it is a little bit more verbose.

Questions

I would prefer the second way, however I am not sure if having 3 spec definitions for a single function would cause issues with dialyzer.

1.Wouldn’t the second approach confuse dialyzer (because I am defining the same spec 3 times)?
2. If the second approach has serious drawbacks, is there a way of improving the spec of the first approach?

Marked As Solved

peerreynders

peerreynders

The issue is that it is a single function - the fact that it is split into multiple clauses is an implementation detail that has nothing to do with the function’s actual type. So if you want tighter typing you need to define separate functions with separate types - but the moment you delegate from the multi-clause function to the specific functions you lose the direct correlation between the input types and the output types on the multi-clause function - because you are creating the respective sum (or union) types for those inputs and outputs. Try as you might:

@spec validate_generator(any) :: :ok | {:error, String.t}

is the effective type of that function. What you are trying to do is tie a specific type to part of a function’s implementation - functions and types simply don’t work that way.

This is one of the reasons why I’ve come around to seeing the mainstream way of typing:

function_name(parameter_name : parameter_type) : return_type

as conflating types with implementation - as in many cases the parameter names are coupled to the implementation. I find myself often in the situation where I know the type of the function before I have good names for all the parameters (and perhaps even the function).

It’s usually helpful to write down the type of new functions first;

Also Liked

LostKobrakai

LostKobrakai

I’m of a different opinion.

While validation of inputs / handling of errors should happen at the points of entry/output of the system this function seems like exactly that: Logic to validate input. At some place there needs to be code handling input, which determines if it conforms to expectations or not. And it’s not unreasonable to expect such code to handle any() input.

If this is part of the business domain then it’s a different picture. In there code should assume it’s supplied what it expects and blow up if that’s not the case.

LostKobrakai

LostKobrakai

This is an example of forms used in other languages.

Last Post!

peerreynders

peerreynders

Correct.

There was a discussion about typing style preferences.

I used to be in the inline typing camp - but at that time I was only concerned about the types of the parameters and really had no awareness of a function’s type. There is such a thing as a function pointer but at the time the typing syntax seemed as mystical as a rune spell.

With JavaScript I discovered using “functions as values” and that is when function types became important - at that point the parameter types were simply derivative of the function type they were part of.

Where Next?

Popular in Questions Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement