[Proposal] Update MapSets with pattern matching, assignments, update shorthand, etc.

155 views
Skip to first unread message

SJ

unread,
Feb 7, 2022, 5:33:23 AM2/7/22
to elixir-lang-core
Hi all.

I have set out a proposal of what these things could potentially look like.


I searched for prior work in this area and couldn't find any. 

If it's a welcome feature I'd be happy to work on it given some direction of where to start.

Thanks for any feedback. 

Best

Adam Lancaster

unread,
Feb 7, 2022, 7:44:43 AM2/7/22
to elixir-l...@googlegroups.com
This is tricky for me. While I am sympathetic to the feature, I think this would be the first time abstraction was added to pattern matching so we'd need to be careful.

Up to now the pattern matching syntax uses the same syntax that you use to create the literal data that is being matched. Eg for a map pattern we write a map

%{a: variable} = %{a: 1}

This brings clarity because the pattern describes the data you are matching on - when you read the pattern you can see right away what the expected data is (a map).

The proposal sketched in the Gist would add indirection to pattern matching by introducing a new syntax - syntax that is specific to one abstract data structure (the map set).

By abstracting away the details of the data structure being matched on we reduce the clarity we usually get, we introduce more syntax into the language, and we open the floodgates
to whether we should have pattern matching on more abstract data types - Ranges, URIs... DateTimes etc. At which point the question really becomes "should we have abstract patterns
in Elixir" which I think is harder to answer.

In the meantime looking at ex_pat might be valuable to you because it would allow you to reach into the details of a MapSet with pattern matching in a controlled way that meant if the implementation
details of a MapSet changes it wouldn't break you program too much.


Just my two cents though!

Best

Adam

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/a73ced4a-5f61-4f40-bbba-8ed2749a3e62n%40googlegroups.com.

José Valim

unread,
Feb 7, 2022, 8:10:39 AM2/7/22
to elixir-lang-core
Hi SV, thank you for the proposal.

I think a more suitable solution to Elixir would be to not introduce a new syntax to mapsets but, instead, find a mechanism to make the constructions above possible. For example, you mentioned ranges, but they are implemented using macros and a regular operator. All of the logic is encoded in Elixir.

Adding new syntax means adding new special forms, new translations from Elixir to Erlang, and so on. For example, you could achieve most of the proposed behaviour by having a "map_set([keys])" macro, that is valid in patterns and can also create new sets outside of patterns. Similarly, a "is_in_set" guard could be added similar to "is_map_key".

The argument against having a map_set/1 macro is that it doesn't feel first-class, like maps or lists. We have discussed potential alternatives to this problem a couple times, and it may be worth revisiting those discussions. For example, there are several data-types, such as URI, Decimal, and MapSet, that are represented using a format that cannot be copied back to Elixir code:

iex(1)> MapSet.new([1, 2, 3])
#MapSet<[1, 2, 3]>

While they could have a portable representation, such as:

%MapSet[1, 2, 3]

But the language does not support such mechanism. If we did address this issue though, it would be more generally usable for other types and it could be generic enough to support most of your use cases (except for syntactical updates, which I think is not that important).


--

Adam Lancaster

unread,
Feb 7, 2022, 8:13:22 AM2/7/22
to elixir-l...@googlegroups.com
>  While they could have a portable representation, such as ...

Another good portable representation would be a sigil, if we could have multi character sigils.

Best

Adam

José Valim

unread,
Feb 7, 2022, 8:21:08 AM2/7/22
to elixir-lang-core
The issue is that sigils do not work on all cases, such as MapSets, because sigil terminators would easily get mixed up with the actual Elixir syntax. So they work for things that have reasonable textual representations (Dates, URIs and Decimal being good examples).

Let's say we had multi letter sigils and the proposed syntax above (I am not saying we should, it is just to understand the differences). A Date as multi-letter sigil could be:

~Date[2022-02-07]

In the syntax from my previous email, it would be:

%Date[2022, 02, 07]

Or:

%Date["2022-02-07"]

So one is textual and the other is structural. The sigil one is unlikely to help with pattern matching though.

Wojtek Mach

unread,
Feb 7, 2022, 8:31:41 AM2/7/22
to elixir-lang-core
Regarding `%MapSet[1, 2]`, I think it looks really nice.

Regarding multi-letter sigils, I think we should have those but not for this particular use case. Sigils are for textual representations so not a good fit for "containers" like MapSet, Vector, etc, when evaluating `%MapSet[...]` we want to evaluate the items inside as _code_ not as _text_ (which the sigil would). There's also the sigil escaping that José mentioned.

This begs the question do we have `%MapSet[1, 2]` AND `~Version[1.0.0]` and I'd say YES but I totally can see why they maybe look a bit too similar with pretty different semantics and thus it's one or the other (or none!)

Regarding the gist,

iex> enum = [:foo, :bar, "set"]
...> %[enum]
%[:foo, :bar, "set"]

I think this should be `%[[:foo, :bar, "set"]]`. :) Otherwise `%[x]` is different than `%[x, y]` which feels unpredictable.

def function_2(%[:foo])

what are semantics of this pattern match? Does it match when the given set is:

1. a subset of `%[:foo]`
2. when it is exactly the same?

# Match and update - like `%{map | key: new_val}`
iex> set = %[%User{id: 1}]
iex> %[set | %User{} => %User{id: 2}]

note, on maps we cannot do `%{map | %User{} => %User{id: 2}}` today. I mean, we can, but it doesn't do what you want it to do, it would try to update a key that `%User{}` evaluates to, something like `%User{id: nil, ...}`. Changing the semantics would be a breaking change. It's unclear how it should work when it matches multiple elements, do we update all of them? That's not obvious! 

José Valim

unread,
Feb 7, 2022, 8:57:33 AM2/7/22
to elixir-lang-core
And for all of those cases, if we the concern is inspections, there is always the option of printing an expression, such as:

MapSet.new([1, 2, 3])
URI.parse("https://foo/bar")
Version.parse!("1.0.0")

It doesn't address the concerns about pattern matching though.

Adam Lancaster

unread,
Feb 7, 2022, 9:08:38 AM2/7/22
to elixir-l...@googlegroups.com
Ah I see about sigils that makes sense thank you for explaining! 

That's a really interesting idea then to kind of surface the parts of the data structure that are public and "pattern matchable" so to speak (if I understand right),

>  if we the concern is inspections ....

That's true, I asked if we could do that in Decimal for example


Best

Adam

Benjamin Danklin

unread,
Feb 7, 2022, 9:19:35 AM2/7/22
to elixir-l...@googlegroups.com
Thank you everyone for the feedback, definitely a lot I didn't think of.

@Wojtek 
Example 1 I took inspiration from the MapSet.new function. where `new/0` returns an empty MapSet and `new/1` takes an enum and enumerates those objects into the new MapSet. 

Example 2 is a subset. which follows the current logic from matching map keys . 
 %{foo: :foo, bar: :bar}
|> case do
  %{foo: foo} -> foo
  %{foo: _foo, bar: bar} -> bar
end
|> IO.inspect()
#=> :foo

Example 3 you are correct. I was incorrect with how I thought keys in maps could be matched and after further review, I agree with Adam that it is too abstract and differs too greatly, it should be disregarded. I will update the gist accordingly. 
Best

You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/RA5uXkilYsE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/EE108E83-AFA1-4C1B-8D14-25757B1F1487%40a-corp.co.uk.

Michał Muskała

unread,
Feb 7, 2022, 10:18:01 AM2/7/22
to elixir-l...@googlegroups.com

I believe that printing expressions is what python usually does in their inspect implementation. It does help a lot with copy & paste when working with the shell.

José Valim

unread,
Feb 7, 2022, 11:45:42 AM2/7/22
to elixir-lang-core
Thank you Michał. I have added an issue to convert some of them to expressions:


I think we should at least give it a try as it is quite "low cost" to try it out.

We should also discuss if we should add is_map_set_member/2 to our set of guards, as it will at least make map sets a bit more part of the language.

Aleksei Matiushkin

unread,
Feb 7, 2022, 12:12:08 PM2/7/22
to elixir-l...@googlegroups.com
Please note, that changing a default `inspect/2` would break all the existing ExUnit.CaptureIO and ExUnit.CaptureLog tests so I’d better start with opt-in through `custom_options: [expressions: true]` or like.



--
Aleksei MatiushkinSoftware Engineer - R&D
 
 


8 Devonshire Square, London, EC2M 4PL, United Kingdom
Torre Mapfre, Planta 22, Marina, 16-18, 08005 Barcelona, Spain

  








LinkedIn    Twitter    YouTube
 
Kantox Limited is a UK private company with registered company number 07657495 and registered address at 8 Devonshire Square, London EC2M 4PL, United Kingdom. We are authorised with the UK Financial Conduct Authority (FCA) under the Payment Service Regulation 2017 as a Payments Institution (FRN 580343) for the provision of payment services and with HMRC as a Money Service Business Registration No.12641987.
Kantox European Union, S.L.  is a Spanish private company with tax ID number B67369371 and registered address at Torre Mapfre, Planta 22, Marina, 16-18, 08005 Barcelona, Spain. Kantox is authorized by the Bank of Spain, with registration number 6890, which is the supervisor of the Spanish banking system along with the European Central Bank. Additionally, we are supervised by SEPBLAC, the Supervisory Authority for the prevention of money laundering and terrorist financing in Spain.
KANTOX is the Controller for the processing of data in accordance with the GDPR and LOPDGDD for the purpose of maintaining a commercial relationship. You may exercise your rights of access and rectification, portability, restriction and opposition by writing to KANTOX to the email: gd...@kantox.com. You have your right to make a complaint at www.aepd.es.  

José Valim

unread,
Feb 7, 2022, 4:59:33 PM2/7/22
to elixir-l...@googlegroups.com
Error messages, inspected representations, etc can all change between releases as they improve, and are documented in our compatibility page. Being unable to improve those aspects would be harmful in the long term. In any case, it rarely changes. :)

Reply all
Reply to author
Forward
0 new messages