Anonymous functions in config: why does it work?

Hi,

I was wondering recently why anonymous functions defined in configuration files (e.g. config/dev.exs) do exist and do work at runtime since they are evaluated at compile-time. What kind of black magic is this?

I’ve been testing it, Elixir actually shows 2 different values for this anonymous function:

  • compile-time: #Function<6.127694169/1 in :erl_eval.expr/5>
  • runtime: #Function<18.127694169/3 in :erl_eval.expr/5>

Cheers

1 Like

Afaik fully quantified closures a.k.a &MyModule.function/1 can be moved from compile time to runtime. All other versions shouldn’t work.

Well I can confirm I can use the following function defined in my config/dev.exs:

config :my_app, :my_key,
  fn x, y, z ->
    case {x, y, z} do
      {_val, _val, _val} ->
        :same

      _ ->
        :but_different
    end
  end

It all depends on how it is ‘used’ as a config, functions work fine in general there, they aren’t stored in module attributes for example. Primarily release time becomes more… troublesome. ^.^

Hay that’s the question I forgot to ask: so this wouldn’t work in releases?

“It Depends”. ^.^;

If it’s only used at building time then it’s fine, otherwise no, and there are other conditionals. ^.^;

1 Like

Thanks for your response.

After investigating, I can confirm that it doesn’t work with Distillery or mix releases.

Regarding mix releases, the problem is that module atom names are not properly escaped when written to the sys.config files. For instance:

{token_store_refresh_token_before_store_callback, fun Elixir.Asteroid.Utils:id_first_param/2},

is written instead of:

{token_store_refresh_token_before_store_callback, fun 'Elixir.Asteroid.Utils':id_first_param/2},

and so you’ll end up with one of the following message (adding it for search engines):

Distillery:

==> Assembling release..
==> Building release asteroid:0.1.0 using environment prod
==> Including ERTS 10.0 from /usr/lib/erlang/erts-10.0
==> Packaging release..
==> Release packaging failed due to errors:
    Cannot add file sys.config to tar file - [{error,{22,erl_parse,["syntax error before: ","'.'"]}}]

Mix releases:

$ MIX_ENV=prod mix release
Compiling 88 files (.ex)
Generated asteroid app
* assembling prod-0.1.0 on MIX_ENV=prod
* skipping runtime configuration (config/releases.exs not found)
** (Mix) Could not read configuration file. It likely has invalid configuration terms such as functions, references, and pids. Please make sure your configuration is made of numbers, atoms, strings, maps, tuples and lists. Reason: {22, :erl_parse, ['syntax error before: ', '\'.\'']}

I’ll report an issue on Elixir’s Github. That said there may be other reasons why allowing named functions in config files are a bad idea? Mix source code seems to exclude this possibility (source) unless it’s not recommended only for anonymous functions.

EDIT: opened github issue

1 Like

Functions are serializable so why would you assume it should not work ?

This is valid erlang:

(binary_to_term(term_to_binary(fun () -> ok end)))().
=> ok

An anonymous function in a configuration file is evaluated at compile time. Then the following is written to the sys.config file (just tested):

{app,[{opt,#Fun<erl_eval.6.127694169>}]},...

which:

  1. is not a valid Erlang term
  2. won’t exist anyway when the release is launched

EDIT:
Configuration is not serialized with term_to_binary/1 but written in a text format and read by the Erlang file:consult/1 function

I know it is not serialized, but it could. Elixir configuration & release system should provide that. It could be stored ad compile time in a module attribute or whereever you fetch the config at compile time.

At run time I do not know. It could work but would have to be compiled somewhere.

Actually couldn’t it work with the the new releases.exs? As stated in the documentation:

If a config/releases.exs exists, it will be copied to your release and executed as soon the system starts. Once the configuration is loaded, the Erlang system will be restarted (within the same Operating System process) and the new configuration will take place.

I’m not sure to understand it well, especially if it means that an anonymous function executed in such a way will be available after the Erlang system restart.

The problem is different for named function, which is just an Erlang lib escaping bug.

1 Like

Definitely a bug! ^.^

1 Like