Nonexistent function capture with &

When using function capture with & I was expecting that the compiler would check if the function exists and the arity is correct but it doesn’t.

If I do

inspect(&Foo.bar/123) in iex console, there’s no error.

Is there a way to validate that the function exists and the arity is correct with function capture?

iex(4)> f=&Foo.bar/1  
&Foo.bar/1
iex(5)> f.(1)
** (UndefinedFunctionError) function Foo.bar/1 is undefined (module Foo is not available)
    Foo.bar(1)
    iex:5: (file)
iex(5)> defmodule Foo do
...(5)> def bar(a) do
...(5)> a+a
...(5)> end
...(5)> end
{:module, Foo, ...}
iex(6)> f.(1)
2
1 Like

Given modules on the beam can become available at any time it’s not necessarily wrong to capture a function for a module, which doesn’t exist/is not compiled. If you need a check consider function_exported?/3.

4 Likes

I think the correct thing is to use the is_function/2 guard, which will ensure that the captured arity is correct.

defmodule Example do
  def example(fun) when is_function(fun, 1) do
    :ok
  end
end

iex> Example.example(&(&1 + 1))
:ok

iex> Example.example(&Foo.bar/1)
:ok

iex> Example.example(&Foo.bar/2)
** (FunctionClauseError) no function clause matching Test.example/1

This will give you a function clause error if the wrong arity is captured, or an UndefinedFunctionError if the right arity is there but the function doesn’t exist or isn’t exported (like Foo.Bar/1).

2 Likes

To clarify, the UndefinedFunctionError will only happen when you invoke it, because as others have pointed, the function can be defined at any moment.

However, note that the Elixir Mix compiler will warn in such cases. It collects all function references and warns once it gathered all modules and invocations.

6 Likes