Returning and executing literal anonymous function

Hi!

First I wanto to say that I have been using Elixir professionally for the past couple of months and it has been a great experience :). Thanks to every person that supports the language.

The issue is:
Today, I was trying to do the following : Given a certain type of parameter to a named function, return a certain literal lambda and use it to perform a query. This is because I’m expecting a JSON with a key named “paginate”, that gets “validated” into a map of the form %{"paginate" => %{"page" => page, "page_size" => page_size}} to determine if a query has to be paginated or not. This map is then used as a parameter for a function named query_chooser:

def query_function_chooser(%{"paginate" => %{"page" => page, "page_size" => page_size}}) do
    (fn query ->
      q_results = Repo.paginate(query, page: page, page_size: page_size)
      q_results.entries
    end)
  end

  def query_function_chooser(_) do
    (fn query -> Repo.all(query) end)
  end

Then, query_function_chooser gets called like this:

  def get_items(params) do
    func = Utils.query_function_chooser(params)

    q =
      from(i in Item,
        select: i
      )

    func.(q)
  end

And with that code, I get the following error: function Repo.all/1 is undefined (module Repo is not available).
If I call Repo.get or Repo.paginate directly from the body of get_items I don’t get such error.

What could be the problem?

Is there a better way to achieve this?

Thanks :slight_smile:

Actually in this case I don’t think you need the anonymous function, just a function call since you are using pattern matching to chose which function clause. For example:

  def maybe_paginate(query, %{"paginate" => %{"page" => page, "page_size" => page_size}}) do
    query
    |> Repo.paginate(query, page: page, page_size: page_size)
    |> Map.get(:entries)
  end

  def maybe_paginate(query, _) do
    Repo.all(query)
  end

  def get_items(params) do
    q =
      from(i in Item,
        select: i
      )

     maybe_paginate(q, params)
  end

Of course your example might just be a simplification of your use case and I may have misunderstood. And of course it doesn’t address your original question. I assume (but have not verified) that you have alias MyApp.Repo in a scope that is not available inside the anonymous functions. But we would have to see the overall module before jumping too much to conclusion.

1 Like

Hi, Kip! :slight_smile:

Yes, this is actually a simplificaton of my problem, query_function_chooser is supposed to be a general function, imported from another module to determine if a pagination is to be done, or not.

And yes, you are are right about alias MyApp.Repo, but the thing is that the anonymous function is expected to be called/used from one of many ModuleNumber.Repo modules. So, If I want to paginate (or not) on the Module0.Repo or the Module1.Repo , I want to use query_function_chooser like in the get_items example.
Because of that, even If I were to use alias MyApp.Repo, I don’t think It would work, because the function could be called from either Module0 or Module 1 (or Module 2, Module3, etc.). Maybe there is a way to make this work that I’m not aware of?

Thans!

Ah, ok, now I understand better. In this case you would likely need to pass in the repo you need to the anonymous function as well. For example:

def query_function_chooser(%{"paginate" => %{"page" => page, "page_size" => page_size}}) do
    (fn repo, query ->
      q_results = repo.paginate(query, page: page, page_size: page_size)
      q_results.entries
    end)
  end

  def query_function_chooser(_) do
    (fn repo, query -> repo.all(query) end)
  end

Then, query_function_chooser gets called like this:

  def get_items(params) do
    func = Utils.query_function_chooser(params)

    q =
      from(i in Item,
        select: i
      )

    # Call the function with the relevant repo
    func.(MyApp.Repo, q)
  end

Would that work in your case?

2 Likes

Yes, passing the module as an argument seems like a good solution. Thanks :slight_smile: