Partially applied functions and placeholder arguments

Hi there,

I am currently learning Elixir by going through the Mix and OTP guide on elixir-lang.org. I have read through the Getting Started guide already.

I am having trouble getting my head around the use of placeholder arguments when capturing and partially applying named functions. The specific examples that are confusing me are on this page here https://elixir-lang.org/getting-started/mix-otp/agent.html

def get(bucket, key) do
    Agent.get(bucket, &Map.get(&1, key))
end
def put(bucket, key, value) do
    Agent.update(bucket, &Map.put(&1, key, value))
end

If I understand correctly, the & in &Map.get and &Map.put is capturing the named functions so that they can be passed as arguments to the Agent.get and Agent.update functions. This makes sense to me. But the &1 is causing confusion.

I think the &1 is evaluating to bucket in each of these examples. I don’t see what else could be happening here. But if this is the case, then why can’t we just write bucket instead of &1? Wouldn’t that be a whole lot clearer?

I guess I could just carry on with the tutorial without properly understanding this point, but it seems like this is a crucial enough concept to ask an embarrassingly noob question on these forums.

Thanks in advance for helping me to understand this.

Hello and welcome,

& is a shortcut for defining anonymous function, and &1, &2… are the parameters to this function.

&Map.get(&1, key)

# is equal to

fn x -> Map.get(x, key) end

You might find version with two parameters, in particular with reduce…

For example.

iex> Enum.reduce [1, 2, 3], & &1 + &2
6

# is equal to

iex> Enum.reduce [1, 2, 3], fn x, acc -> x + acc end

More on this here

4 Likes

Hello kokolegorille,

Thanks for your reply and the explanation of how the ampersand & works. In general, it makes sense to me, like in the following example:

iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2

I have no problems with that.

However, I am still confused by the examples of code in my original question, though, so let me try to ask more specifically about what I’m confused about.

What is the value in the placeholder &1 being passed to the function &Map.get in the following example?

def get(bucket, key) do
    Agent.get(bucket, &Map.get(&1, key))
end

It is the state of the Agent that get passed to the anonymous function.

The state of the agent? Can you please elaborate?

An agent store some state…

https://hexdocs.pm/elixir/Agent.html#get/3

Oh! Now I see. The &1 is the Agent's state, which is being passed from the Agent to the anonymous function.

Maybe its just me, but that seems to be a quite different behaviour for the &1 than it had in the other examples.

1 Like

There is nothing magic about how &1 is working here. Rather, it’s the Agent.get function that is doing the work. &Map.get(&1, key) is just an anonymous function that takes a map and grabs a key from it. For example:

iex(1)> key = :name            
:name
iex(2)> fun = &Map.get(&1, key)
#Function<7.91303403/1 in :erl_eval.expr/5>
iex(3)> map = %{name: "Ben", country: "USA"}
%{country: "USA", name: "Ben"}
iex(4)> fun.(map) 
"Ben"

In this example, &1 becomes whatever you pass to it, in my case map

When you do:

Agent.get(bucket, &Map.get(&1, key))

This is exactly equivalent to just doing:

fun = &Map.get(&1, key)
Agent.get(bucket, fun)

where fun in that example is 100% identical to the fun in my example.

SO how does it get the process state? Well, that’s the job of Agent.get. It takes that anonymous function sends it to the process, the process calls fun with its state, and sends the result back to you.

5 Likes

That is the power of function as first class citizen, You can pass the implementation as an argument :slight_smile:

2 Likes

The capture operator is just a different way to write anonymous functions, but it is perfectly equivalent to the fn ... -> ... end form. The &1, &2, etc. are nothing else that the first argument, the second argument, etc.:

# This:
fun = fn a, b ->
  a + b
end

# Is the same as this:
fun = &(&1 + &2)

The &1, &2, etc. have no direct relation with the context around the function. They are merely the arguments explicitly passed to the anonymous function when it is called:

fun(46, 58) # &1 = 46, &2 = 58
#=> 104

That said, I do think that the capture operator often makes code more obscure, so I personally prefer to use the extended fn ... -> ... end form, even if that is slightly longer.

1 Like

Thanks for the explanations, folks.

I have added a pull request to the documentation over at Github adding a clarifying sentence about this behavior of the Agent.

1 Like