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.

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
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
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
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
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
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
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
vegabook
I'm brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
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
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47849 226
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement