Help with passing and calling functions in metaprogramming

Hi everyone, time for my second topic as I’m learning Elixir. In my upcoming library you can add permission types like “role” which is tied to a callback for checking that permission type. It seems to me like an efficient way of declaring these types would be in mix.exs as env variables, so that these can be resolved at compile time. My first question is, am I on the right path there or would you do it differently?

Now, I’ve been playing around with this a bit and I can’t get it to work like I want it to. I’m almost there but not quite. Here’s a sample env structure:

env: [
  logical_permissions: %{
    permission_types: %{
      role: &LogicalPermissions.role_callback/2
    }
  }
]

The function LogicalPermissions.role_callback/2 is just a test callback that I have defined like this in my module LogicalPermissions:

  def role_callback(value, context) do
    "value: #{value}, context: #{context}"
  end

In the same module, I have this code to generate one “process” function for each permission type that is declared. The idea is that the “process” function should then call the callback that was defined in the env variable.

permission_types = Application.get_env(:logical_permissions_app, :logical_permissions).permission_types

for {key, callback} <- permission_types do
  def unquote(:"process")(unquote(key), value, context) do
    apply(unquote(callback), [value, context])
  end
end

Is it possible to do this in Elixir? If so, what am I doing wrong and what would be the best way to do it?

It’s entirely possible and your code should work as long as your environment is populated properly. :slight_smile:

Thanks for the feedback, I keep getting weird error messages but at least now I know that it’s supposed to work, so I’ll just keep on trying.

Do post them with simplified reduced reproducible testcases that we can optimally copy/paste into iex. :slight_smile:

Thanks, I’ll do that if I get stuck. I really appreciate your support but I don’t want to take up your time unless absolutely necessary.

It’s not just that, such errors and discussion can help others too! :slight_smile:

Good point. Well, I think I’ve managed to isolate an error in a minimal setup, so here it is. I created a new project and just added the env part in mix.exs and then a callback in the module. It does compile when you run “mix compile”, but when you try to enter “iex -S mix” you get the following error message:

** (Mix) Could not start application project: {[‘7’, 58, 32, ['syntax error before: ', [‘Fun’]]], ‘project.app’}

Here’s the content of the files:

mix.exs

defmodule Project.MixProject do
  use Mix.Project

  def project do
    [
      app: :project,
      version: "0.1.0",
      elixir: "~> 1.6",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      env: [
        test_var: &Project.test_callback/0
      ],
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end

lib/project.ex

defmodule Project do
  def test_callback() do
    true
  end
end

I don’t think anonymous functions are able to be serialized into the application environment, only assigned. You’d need to use an MFA tuple or so instead.

Thanks, I’ll look into that.

It indeed works if you set the value in the environment variable to a tuple with the module and function, and then use apply/3 instead of apply/2 in the generated function. Thanks!