Matching tuples of arbitrary size (was: Re: Regular expressions)

686 views
Skip to first unread message

Avdi Grimm

unread,
Dec 10, 2013, 11:12:12 PM12/10/13
to mraptor, elixir-l...@googlegroups.com
On Tue, Dec 10, 2013 at 9:03 PM, mraptor <mra...@gmail.com> wrote:
 
Thanks! Those docs bring to mind another, unrelated question. They say:

The mp() is guaranteed to be a tuple() having the atom 're_pattern' as its first element, to allow for matching in guards. The arity of the tuple() or the content of the other fields may change in future releases.

That almost suggests to me that in Erlang it's possible to match on the first part of a tuple of arbitrary length, e.g. the following pseudocode:

    {:foo, some_var, *} = data

I've been wondering lately if this is possible in Elixir, particularly as I was dealing with some large xmerl tuples where I didn't care about most of the fields in the tuple, winding up with something ugly like this:

    {:foo, some_var, _, _, _, _, _} = data

I realize that since a tuple's size is effectively part of it's type (at least in many languages), this kind of "match the rest" wildcard is problematic, but the docs referenced above gave me some hope, so I thought I'd ask.

--
Avdi Grimm
http://avdi.org

I only check email twice a day. to reach me sooner, go to http://awayfind.com/avdi

Christopher Keele

unread,
Dec 10, 2013, 11:38:31 PM12/10/13
to elixir-l...@googlegroups.com
I don’t believe so; tuples are contiguous chunks of VM memory with constant cardinality. I think pattern matching has to contend with that at runtime.

The arity of the tuple() or the content of the other fields may change in future releases.
Sadly, I think that’s just an over-earnest warning about a contemplated backwards-incompatible API change. I’ve stumbled on a few passages of erlang docs that seem really unsure about their future arities. (Confident Erlang, maybe?)

On the other hand, you can interrogate specific parts of tuples agnostic of their size with `elem`, which is available in guards:

```elixir
def process_xmerl_tuple(data) when is_tuple(data) and elem(data, 0) == :foo do
  # take that xmerl!
end
```

If you lead your targets and get clever with your functions and clauses, you could offload any pattern-matching-driven conditional branching into guards. An orchestra of `is_tuple`, `elem`, and `tuple_size` could serve your purposes. It still won’t be as pretty or concise as pattern matching, though.

Another option is to have a pre-processor style wrapper function that calls `tuple_to_list` before passing it on to your processing logic. A little extra memory, a lot more expressiveness. Also not as satisfying as variable tuple matching. 

I’d love a definitive answer to this as well.

-- 
Christopher Keele
Sent with Sparrow

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Avdi Grimm

unread,
Dec 11, 2013, 12:46:47 AM12/11/13
to elixir-l...@googlegroups.com
With what (little) I understand of metaprogramming in Elixir, I'm guessing it wouldn't be too hard to create something along these lines:

```elixir
  deftuple Widget, foo: :x, bar: nil, baz: nil, buz: nil
  # ...
  tmatch Widget[bar: 23, buz: 42] = data
```

Where the last line would expand out to:

```elixir
  {:x, _, 23, _, 42} = data
```

But I'm probably missing something.
  

Avdi Grimm

unread,
Dec 11, 2013, 12:47:56 AM12/11/13
to elixir-l...@googlegroups.com

Oops, forgot to markdown-ify that last email…

  deftuple Widget, foo: :x, bar: nil, baz: nil, buz: nil
  # ...
  tmatch Widget[bar: 23, buz: 42] = data

  {:x, _, 23, _, 42} = data

Eric Meadows-Jönsson

unread,
Dec 11, 2013, 1:27:04 AM12/11/13
to elixir-l...@googlegroups.com

What you wrote is how elixir implements records (with some extra magic for record dispatch).

iex(1)> defrecord Widget, foo: :x, bar: nil, baz: nil, buz: nil
{:module, Widget,
 <<70, 79, 82, 49, 0, 0, 22, 144, 66, 69, 65, 77, 65, 116, 111, 109, 0, 0, 1, 14, 0, 0, 0, 33, 13, 69, 108, 105, 120, 105, 114, 46, 87, 105, 100, 103, 101, 116, 8, 95, 95, 105, 110, 102, 111, 95, 95, 4, 100, 111, ...>>,
 nil}

iex(2)> Macro.expand(quote do Widget[bar: 23, buz: 42] end, __ENV__.context(:match)) |> Macro.to_string
"{Widget, _, 23, _, 42}"
Eric Meadows-Jönsson

José Valim

unread,
Dec 11, 2013, 2:16:28 AM12/11/13
to elixir-l...@googlegroups.com
Sadly, I think that’s just an over-earnest warning about a contemplated backwards-incompatible API change. I’ve stumbled on a few passages of erlang docs that seem really unsure about their future arities. (Confident Erlang, maybe?)

Given how seriously the OTP team takes backwards compatibilty, that's very likely an outdated warnings.

They include those warnings when the feature is experimental but probably some of them were missed when the feature got effectively stable.  
 
On the other hand, you can interrogate specific parts of tuples agnostic of their size with `elem`, which is available in guards:

```elixir
def process_xmerl_tuple(data) when is_tuple(data) and elem(data, 0) == :foo do
  # take that xmerl!
end
```

You can't pattern match on a tuple dynamically. The best thing to do, in this case, is to simply add the guard clauses as you did. The plus thing is, if you are in a guard, you don't need to check for the tuple_size. If you try to access an element that does not exist, it will move to the next one (i.e. errors in guards do not leak).

Vincent Siliakus

unread,
Dec 11, 2013, 2:48:26 AM12/11/13
to elixir-l...@googlegroups.com
Op woensdag 11 december 2013 05:38:31 UTC+1 schreef Chris Keele:

I’d love a definitive answer to this as well.

Maybe I'm stating the obvious and it doesn't help in Avdi's case, but this is one of the reasons records exist. You can add a field to a record, something that changes the arity of the underlying tuple, without breaking code that uses the record.

-vincent

Avdi Grimm

unread,
Dec 11, 2013, 10:50:56 AM12/11/13
to elixir-l...@googlegroups.com

On Wed, Dec 11, 2013 at 2:16 AM, José Valim <jose....@plataformatec.com.br> wrote:
The plus thing is, if you are in a guard, you don't need to check for the tuple_size. If you try to access an element that does not exist, it will move to the next one (i.e. errors in guards do not leak).

Hey, that's good to know!
Reply all
Reply to author
Forward
0 new messages