Skip to content

Domain keys in map #14478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Domain keys in map #14478

wants to merge 7 commits into from

Conversation

gldubc
Copy link
Member

@gldubc gldubc commented May 9, 2025

This allows maps to encode domain keys, from paper https://www.irif.fr/~gc/papers/icfp23.pdf

For example, we can now express the type %{a: integer(), b: float(), atom() => tuple()} of maps that have keys :a and :b and any other atom key mapping to a tuple.

Those are created by adding {{:domain_key, :atom}, type} pairs to constructors open_map or closed_map.

This introduces new operators:

  • map_get, which computes the type obtained with expression map[key] where key is some expression of some type
  • map_refresh, which computes type the map obtained when doing Map.put(map, key, type) (not called map_put because the name is already used for adding a single key to a map. The difference here is that, key being a type, we are not sure which key is being added).

Both operators handle singleton atoms exactly. See the following tests:

 test "more complex map get over atoms" do
      map = closed_map([{:a, atom([:a])}, {:b, atom([:b])}, {{:domain_key, :atom}, pid()}])
      assert map_get(map, atom([:a, :b])) == {:ok_present, atom([:a, :b])}
      assert map_get(map, atom([:a, :c])) == {:ok, union(atom([:a]), pid() |> nil_or_type())}
      assert map_get(map, atom() |> difference(atom([:a, :b]))) == {:ok, pid() |> nil_or_type()}

      assert map_get(map, atom() |> difference(atom([:a]))) ==
               {:ok, union(atom([:b]), pid() |> nil_or_type())}
    end

  # Tricky cases with atoms:
  # We add one atom fields that maps to an integer, which is not :a. So we do not touch
  # :a, add integer to :b, and add a domain field.
      assert map_refresh(
               closed_map(a: pid(), b: pid()),
               atom() |> difference(atom([:a])),
               integer()
             ) ==
               {:ok,
                closed_map([
                  {:a, pid()},
                  {:b, union(pid(), integer())},
                  {{:domain_key, :atom}, integer()}
                ])}

Notes:

  • I have not tested enough the optimizations to know if they can be adapted to domains.
  • One operator missing is map_delete.
  • Function map_check_domain_keys(tag, neg_tag) is very long to treat all the different cases optimally. It could be shortened to a single loop on all the possible domains, with default values.

gldubc added 5 commits May 6, 2025 14:33
- Introduced tests for union, intersection, and difference operations involving domain key types.
- Validated subtype relationships and intersection results for maps with domain keys.
- Enhanced map fetch and delete functionalities to handle domain key types.
- Ensured correct behavior of dynamic types with domain keys in various scenarios.
…n_map_with_default to map_with_default and implement map_update functionality
@josevalim
Copy link
Member

I will lock this issue for now because of spam, will reopen once I have feedback. :(

@elixir-lang elixir-lang locked as spam and limited conversation to collaborators May 10, 2025
@josevalim josevalim added this to the v1.19 milestone May 11, 2025
@elixir-lang elixir-lang unlocked this conversation May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants