Pattern matching during pipe isn't working

If you have a look here:

locations= user_ids
|> Enum.map(&Task.async(fn -> last_location(&1, params) end))
|> Enum.map(&Task.await/1)
|> Enum.map(fn [status] -> status end)

this will work fine, if the result before the last pipe is like this for example:

[
  [{"utc_unix_time":1488180252,"user_id":15,"long":35.91577274638908,"lat":31.82970170499193}],
  [{"utc_unix_time":1488180252,"user_id":16,"long":35.91577274638908,"lat":31.82970170499193}],
]

however, if the result was empty, like this:

[ [], [] ]

then, I had to do a case to handle both cases, here what I have tried to do:

locations= user_ids
|> Enum.map(&Task.async(fn -> last_location(&1, params) end))
|> Enum.map(&Task.await/1)
|> case do
[] -> []
_ -> Enum.map(fn [status] -> status end)
end

but, I got this error:

function Enum.map/1 is undefined or private. Did you mean one of:
      * map/2

I thought I can pass the missing arg from pipe as:

|>  case do
  [] -> []
  _ -> Enum.map(&1, fn [status] -> status end)
  end

but I got error:

: unhandled &1 outside of a capture

Any advice?

1 Like

Move the logic of the case statement in the function of your mapping operation, like below

fn
  {:a, :b} = tuple ->
    IO.puts "All your #{inspect tuple} are belong to us"
  [] ->
    "Empty"
end
2 Likes

My advice would be to put the case statement in its own function.

You can make it an inline function like @LostKobrakai suggests, but your code will become more readable if you put it in its own named function, especially since this also allows you to get rid of the case and just use pattern-matching at the function level:

def some_function()
  # ... more code above?
  locations= user_ids
  |> Enum.map(&Task.async(fn -> last_location(&1, params) end))
  |> Enum.map(&Task.await/1)
  |> process_result()
end

def process_result([]), do:  []
def process_result(input), do: Enum.map(input, fn [status] -> status end)
3 Likes

thanks for great suggestions

2 Likes

I agree that putting the case in a function is the prettiest, but to answer the actual question as it was asked, the problem with this:

is that the “catch all” branch of the case has the value assigned to “_”, which is the “ignore this value” variable name (well … anything starting with an _, really). So there is no variable to bind. It should be something like:

Note how now the 2nd case branch is capturing the value into the loc variable … and now it can be used :slight_smile:

3 Likes

If you really want to keep this, using an anonymous function, you need to remember that you can pattern match in them multiple times as well.

So the following should do what you want:

foo
|> Enum.map(fn
  [x] -> x
  []  -> []
end)

This will convert [[], [:a]] into [[], :a], [[], []] into [[], []] and [[:foo], [:bar]] into [:foo, :bar] which seems to be what you want from your first post.

PS: It might be, that Enum.concat/1 does pretty well what you need, also one could perhaps use Enum.flat_map/2: foo |> Enum.flat_map(&(&1)) (both filter out the empty lists though).

3 Likes