7

I am having trouble understand the '&' operator in the following context.

  @doc "Marks a task as executed"
  def put_task(task, project) do
    item = {task, project}
    Agent.update(__MODULE__, &MapSet.put(&1, item))
  end

It seems that in this case the '&1' is referring to the map object itself, but I am curious as to how this works. Is it passing itself in as an argument I looked into this in the docs but couldn't find out if this was exactly what was going on. I would be grateful if somebody could help me understand what is exactly going on and what &1 refers to and if it refers to the MapSet how is this possible.

Paweł Obrok
  • 22,568
  • 8
  • 74
  • 70
cogle
  • 997
  • 1
  • 12
  • 25
  • 1
    That's equivalent to `fn x -> MapSet.put(x, item) end`. I'm sure there's an existing question about this here on StackOverflow but I can't seem to find it. – Dogbert Jun 23 '16 at 04:33
  • My question is what is x in this case. – cogle Jun 23 '16 at 05:35
  • Does this answer your question? [Why are there two kinds of functions in Elixir?](https://stackoverflow.com/questions/18011784/why-are-there-two-kinds-of-functions-in-elixir) – Amin Soheyli Dec 26 '20 at 10:23
  • Link to pertinent doc: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1 Read the section "Anonymous functions" – Roland Aug 26 '22 at 11:46

3 Answers3

23

The &1 is the first argument of the function. The whole & notation is basically an alternative way to express anonymous functions - there's nothing specific to Enum or Agent about it. Let's take this example:

fn (x, y, z) -> (x + z) * y end

This is an anonymous function that takes 3 arguments, adds the first and third one and multiplies the result by the second one. With the & notation:

&((&1 + &3) * &2)

Think of the &1, &2 and &3 as placeholders in an expression where the arguments will go. So when you do

Agent.update(__MODULE__, &MapSet.put(&1, item))

You're calling Agent.update with a one-argument function that calls MapSet.put with that argument and item - whatever that is. It's equivalent to:

Agent.update(__MODULE__, fn x -> MapSet.put(x, item) end)
Paweł Obrok
  • 22,568
  • 8
  • 74
  • 70
5

Lets consider a more simple example,

Enum.map [1, 2, 3, 4], &(&1 * 2)

Enum.map [1, 2, 3, 5], fn x -> x * 2 end // Exactly like above

To Enum.map we are basically passing a list and an anonymous function. Enum.map expects the anonymous function to have atleast one argument(no brainer, it passes each element of the list to anonymous function). So here &1 which is the first argument of the anonymous function which will be set by Enum.map when it calls our anonymous function. Enum.map loops through the list calling our anonymous function each time with a different element of our list.

Though I do not know the implementation of the Agent.update, but as far as I see what its doing is, Agent.update is calling on the agent which is _MODULE_ and on this applying function Map.Set which receives the old state of the agent, and sets new state of the agent. In other words you could say &1 is the old state of the agent

coderVishal
  • 8,369
  • 3
  • 35
  • 56
  • So for the map it makes more sense to me I know that it will loop through 1 then 2 and so on. But above we are dealing with put. @Dogbert says that the code can be expresses as `fn x -> MapSet.put(x, item) end` which I wasn't confused about in the first place; rather I was confused about what is `x` in this case and if `x` is the MapSet object how is it getting this reference through `&1`. I should have been clearer in the question. – cogle Jun 23 '16 at 05:38
4

I would be grateful if somebody could help me understand what is exactly going on and what &1 refers to and if it refers to the MapSet how is this possible.

Agent.update/3 calls the given function with the current state, and stores the returned value as the new state. Since &MapSet.put(&1, item) is the same as fn x -> MapSet.put(x, item) end, x here becomes the old state, and the new MapSet returned by MapSet.put/2 becomes the new state of the agent.

For this code to function, there must be a call to Agent.start or Agent.start_link with name: __MODULE__ and a function that returns {:ok, map_set} where map_set is any MapSet somewhere in the code.

Dogbert
  • 212,659
  • 41
  • 396
  • 397