2
votes

When using the pipe operator in Elixir, is it possible to be selective/specific about the output that is passed from the first function to the second?

For example, I'm processing a map in three steps - I need to drop two keys, update the value of a different key, and finally pop another key, whose value I need to use later on in my code. Here's an illustration of what I'd ideally be able to achieve:

for r <- records do
  {scope, record} = 
    Map.drop(r, [:__struct__, :__meta__])
    |> Map.get_and_update(:id, fn current_value -> 
      {current_value, String.replace(current_value, "join", "scope", global: false)}
    end)
    |> Map.pop(:user_id)

  # SOME OTHER STUFF...

end

So that'd drop the :__struct__ and :__meta__ keys, update the value of the :id key by replacing the word "join" with "scope", and then pop the :user_id key off, leaving me with {scope, record}, where scope is the value of the popped key and record is the modified map. The code, as is, does not work, of course.

Here's a piece of sample data:

records = [
  %{__struct__: "foo", __meta__: "bar", id: "resource_join:1234", user_id: "user:1234"},
  %{__struct__: "foo", __meta__: "bar", id: "resources_join:5678", user_id: "user:5678"},
]

The first step in this process works, because drop/2 returns just the modified map, which is the correct input for the first parameter to get_and_update/3. However, get_and_update/3 returns a tuple where the first value is the value that that the function replaced and the second value is the modified map. This is where the pipe chain fails, because pop/2 expects a map as the first argument, and here it's receiving a tuple.

Is there a way that I can control the output of get_and_update/3 so that only the second value from the returned tuple is passed through the pipe to pop/2? I've been able to get the output that I need by breaking this chain up into several pieces, but if it were at all possible to chain these together, I'd like to.

1
Consider including data example, so anyone could run your code in consoledenis.peplin
Have you looked into using with in combination of what you have there?Máté
Do you mean like adding |> elem(1) before |> Map.pop(...)? There's also Map.update if you don't care about the old value in Map.get_and_update.Dogbert
@Dogbert right on, thanks for pointing out elem() - didn't know about that. I'll probably end up going with update(), but elem() technically answers my question.skwidbreth
@denis.peplin - good call, I've added a piece of sample data.skwidbreth

1 Answers

2
votes

Since you want to discard the first element of the tuple and only pass the second to Map.pop, you can add |> elem(1) to the pipeline:

|> Map.get_and_update(:id, fn current_value -> 
  {current_value, String.replace(current_value, "join", "scope", global: false)}
end)
|> elem(1)
|> Map.pop(:user_id)

Also, Map.get_and_update + elem(1) can be replaced with just Map.update, which doesn't return the old value of the key:

|> Map.update(fn current_value -> 
  String.replace(current_value, "join", "scope", global: false)
end)
|> Map.pop(:user_id)