Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir: Pattern match a list of specific type?

Tags:

elixir

I have a list of structs of an arbitrary size.

Let's call it l.

l = [%X{a:1}, %X{a:3}, %X{a:9}, %X{a:11}]

The size of l keeps changing. What I'd like to know is how do I pattern match against l to ensure it always is made up of structs of %X{}. I want the pattern matching to fail if the list contains something else. For example:

l = [%X{a:1}, %X{a:3}, %Y{a:9}, %Z{a:11}]

Things I've tried

i = %X{}
j = %Y{}

[%X{}|_] = [i,i,i]

But that matches only the first element.

[%X{}|_] = [i,j,j]

Should fail for my use case, but it doesn't. Maybe if there's an operator or something like this, that will match a list of specific type, that's exactly what I'm looking for:

[%X{}+] = [i,i,i] # Doesn't exist, just an example

Some background

I'm on phoenix and I have a model post with has_many relationship with images. A given user could upload multiple images and I'd like to pattern match to make sure I'm working with the right struct (%Plug.Upload{}) in this case.

Any help is much appreciated. Thanks :)

like image 209
dsignr Avatar asked Sep 18 '18 05:09

dsignr


3 Answers

You cannot pattern match on every element of a list (without recursion). In this case I'd use Enum.all?/2 and the match?/2 macro:

if Enum.all?(list, &match?(%X{}, &1)) do
  ...
end
like image 85
Dogbert Avatar answered Nov 11 '22 02:11

Dogbert


While the answer by @Dogbert is perfectly valid, I personally find the explicit clauses to be more succinct:

all_are_x =
  Enum.all?(list, fn
    %X{} -> true
    _ -> false
  end)
like image 3
Aleksei Matiushkin Avatar answered Nov 11 '22 01:11

Aleksei Matiushkin


AFAIK, what you are looking for doesn't exist: you cannot pattern match on every element of a list.

You can use Enum.map/2, to crash at the first non %X{} element:

Enum.map(l, &(%X{}=&1))

To pattern match, I used: %X{} = something, while Dogbert used: match?(%X{}, &1)

The difference is that first one fails if it doesn't match, while the second one returns false. If you want to stick to "let it crash" of elixir, you might be interested in the first one, while in most of the case you will prefer to use the second one, like this for example:

k == Enum.reject(l, &match?(%X{}=&1))
k == [] || IO.inspect(k)

Enum.reject?/2 used with match?/2 will not crash and return a list of all the element that are not an X structure.

like image 1
Nathan Ripert Avatar answered Nov 11 '22 00:11

Nathan Ripert