Ran into this situation which had me pretty confused for a while.
I had this incorrect code:
Timex.set(microsecond: 0)
Which caused this error:
** (FunctionClauseError) no function clause matching in Timex.normalize/2
The following arguments were given to Timex.normalize/2:
# 1
:microsecond
# 2
0
Attempted function clauses (showing 10 out of 12):
def normalize(:date, {year, month, day})
def normalize(:year, year) when year < 0
def normalize(:year, year)
def normalize(:month, month)
def normalize(:time, {hour, min, sec})
def normalize(:time, {hour, min, sec, ms})
def normalize(:hour, hour)
def normalize(:minute, min)
def normalize(:second, sec)
def normalize(:millisecond, ms)
Showing 10 clauses usually would be enough, however the clause I was trying to match was the 11th clause of 12.
Not quite realising the behaviour of this message (only showing max 10 clauses) I assumed all clauses were shown, and headed off down a less than fruitful avenue of debugging.
It was only when I happened to notice the (showing 10 out of 12) that I realised that the missing microsecond clause must be missing, so found the correct match in the Timex source code.
def normalize(:microsecond, {us, p})
So my code needed to be:
Timex.set(microsecond: {0, 0} )
Should this be showing all clauses? The error message is already pretty long and not showing all clauses (and not having any obvious way to show all if needed) is likely to be confusing.
On the downside there is the possibility of there being many clauses listed. Might it be possible to try and figure out which clauses are closest?
Options:
Original feature was introduced in https://github.com/elixir-lang/elixir/pull/6127 and the limit of 10 is hard coded and not configurable in any way.
The issue is that some functions have hundreds or thousands of clauses. We can add however some visual indication that we are not showing all of them? Maybe:
def normalize(..., ...)
As the last entry?
Another proposal:
** (FunctionClauseError) no function clause matching in Timex.normalize/2
The following arguments were given to Timex.normalize/2:
# 1
:microsecond
# 2
0
Attempted function clauses (showing 10 out of 12):
def normalize(:date, {year, month, day})
def normalize(:year, year) when year < 0
def normalize(:year, year)
def normalize(:month, month)
def normalize(:time, {hour, min, sec})
def normalize(:time, {hour, min, sec, ms})
def normalize(:hour, hour)
def normalize(:minute, min)
def normalize(:second, sec)
def normalize(:millisecond, ms)
(2 clauses not shown)
I definitely prefer yours @fertapric.
I have a couple of libs that generate hundred or thousands of clauses and debugging when there is a function clause error is definitely challenging. So much that I use this gist to help out when needed. Of course the code in the gist is just a slightly modified version of your implementation.
I wonder if it would be possible to open up the blame API for this case so that a developer can invoke blame on a function call and return the full list if they want and the exception says something like
Attempted function clauses (showing 10 out of 12):
def normalize(:date, {year, month, day})
def normalize(:year, year) when year < 0
def normalize(:year, year)
def normalize(:month, month)
(2 clauses not shown)
execute FuncionClause.Blame(M, f, a, limit: n) in iex to return n clauses
n could be number of clauses, defaulting to :infinite.
@kipcole9 I believe your helper function is the way to go for now as printing thousands of clauses is not always helpful. For example, it wouldn't provide any insight for binary matching and the functionality is there if you want to build on top of (as you do :+1:).
Really like the alternatives presented here: emphasising the missing clauses at the bottom of the top 10 list is key to minimising confusion. Providing a clear path to go and expand the list as needed is also important to debugging corner cases.
What about performing a diff between each possible head and the value and just be sure to always show whatever seems closest as well, even if separated by ...'s on the line before and after if it is far down, like:
** (FunctionClauseError) no function clause matching in Timex.normalize/2
The following arguments were given to Timex.normalize/2:
# 1
:microsecond
# 2
0
Attempted function clauses (showing 10 out of 12):
def normalize(:date, {year, month, day})
def normalize(:year, year) when year < 0
def normalize(:year, year)
def normalize(:month, month)
def normalize(:time, {hour, min, sec})
def normalize(:time, {hour, min, sec, ms})
def normalize(:hour, hour)
def normalize(:minute, min)
def normalize(:second, sec)
def normalize(:millisecond, ms)
(1 clauses not shown)
def normalize(:microsecond, {us, p})
The problem is to define what "close" means. For example, both the :year and :month clauses match the same number of arguments as the failed :microsecond one.
Sure, we can come up with heuristics, but that would be a lot of work and it would certainly have a high rate of false positives. For example, if the mistake was due to a typo like :mikrosecond, then any idea of closeness is not helpful (unless you perform myers_diff in some cases, which then adds even more complexity).
If someone thinks this is easy, then by all means go ahead and implement it, and we can provide a way to make the function blame pluggable, but I doubt it is a generally solvable problem.
Happy to put together a PR to capture the discussion so far.
I think it's reasonable to try and suggest some close matches, if possible. Agree that you'll never get it perfect, but if you save some debugging time then it's worth doing.
A PR that follows @fertapric suggestion would be welcome. At the moment, we
José Valimwww.plataformatec.com.br
http://www.plataformatec.com.br/Founder and Director of R&D
Most helpful comment
Another proposal: