Compiler tells me anonymous functions can not have default parameters

So something like this fails to compile:

def streamfun_from_string(str) do
  fn(skip_headers \\ true) ->
    ..
  end
end

I am wondering why is this a restriction? I do not quite understand the reason for it. Is there a work-around?

1 Like

Someone with more knowledge will hopefully chime in, but here we go:

The work around is using a private function:

defp process_headers(skip_headers \\ true) do
  ...
end

When you declare a function with default params, it is basically syntactic sugar for

defp process_headers(skip_headers) do
  ...
end

defp process_headers(), do: process_headers(true)

This isn’t possible with anonymous functions as they are used “as is”: they can only have one arity (compared to named functions where a new function can be declared by the compiler with the same name, but a different arity).

1 Like

Google Groups elixir-lang-talk (2014): Default arguments on anonymous functions

i.e.

defmodule Demo do
  def identity(x \\ :ok), # ONE function definition
    do: x

  defp info(fun) do
    fun
    |> Function.info()
    |> IO.inspect()
  end
  def run do
    info(&identity/0)  # TWO functions
    info(&identity/1)  #
  end
end

Demo.run()
$ elixir demo.exs
[
  pid: #PID<0.89.0>,
  module: Demo,
  new_index: 0,
  new_uniq: <<135, 214, 25, 143, 240, 101, 78, 237, 14, 92, 136, 181, 46, 33, 8,
    18>>,
  index: 0,
  uniq: 71217356,
  name: :"-run/0-fun-0-",
  arity: 0,
  env: [],
  type: :local
]
[
  pid: #PID<0.89.0>,
  module: Demo,
  new_index: 1,
  new_uniq: <<135, 214, 25, 143, 240, 101, 78, 237, 14, 92, 136, 181, 46, 33, 8,
    18>>,
  index: 1,
  uniq: 71217356,
  name: :"-run/0-fun-1-",
  arity: 1,
  env: [],
  type: :local
]

An anonymous function is a single function value.

Is there a work-around?

Probably not what you had in mind:

defmodule Demo do
  def makeFun() do
    fn options ->
      {skip_headers, _options} = Keyword.pop_first(options, :skip_headers, true)
      skip_headers
    end
  end

  def run do
    f = makeFun()
    IO.inspect(f.([]))
    IO.inspect(f.(skip_headers: false))
  end
end

Demo.run()
$ elixir demo.exs
true
false
2 Likes

Perhaps this can help as a quasi-work-around, but it still requires an argument be passed, even if it is nil:

fn ->
  false ->
    do_something_when_false()
  _ ->
    do_something_when_true()
end  

To sum up:

Functions are named by their atom name and their arity. There is no such thing as an arity-less function. There is such a thing as an atom-less function, those are anonymous functions, notice it still has an arity however. Thus every function always absolutely has an arity. Now considering that a default argument actually creates two function of one with the full arity and one of less that just calls the one with more, and considering function pointers only reference a single function and not multiple, that means it can’t encode both, just not possible.

4 Likes

This makes sense to me. Some solutions to this would be using a last parameter which was a Keyword, so that:

f = fn args -> 
 # ...
end

f.([]); # call with no args
f.(extra: 10) # call with an argument

Another option is to not use an anonymous function but rather use a MFA triplet (module, function, args). In this case use Kernel.apply/3 to call your “anonymous function”.

https://hexdocs.pm/elixir/Kernel.html#apply/3

1 Like

Common Pattern in Elixir yep.

And this is the Erlang way and the way that I prefer. :slight_smile:

But given that mfargs triplets don’t have a closure you have to stuff everything into the args list. And given that args are positional, optional arguments can be a bit of a pain unless the head of args is a tuple list.

Or am I missing something?

FYI: For typespecs mfa() refers to {module(),atom(),arity()}. The supervisor documentation uses mfargs() = {M :: module(), F :: atom(), A :: [term()] | undefined}.

1 Like

The common erlang idiom is to do {module, function, [postargs]} where postargs are useful for carrying additional state (I.E. the closure part). So you can do something like {Blah, :blorp, [1, 2, 42]} then call it like {mod, fun, args} = {Blah, :blorp, [1, 2, 42]}; apply(mod, fun, [:prefix, :args | args]).

This is also why you tend to see so many erlang functions designed to take their most useful state at the end (same pattern as piping, which, again, Elixir does backwards), and it fit is so fantastically with Tuple Calls as it basically made this pattern a full callable instead (like anonymous functions)! But that’s gone now *grumble*grumble*…

2 Likes