Skip to content

Local open in patterns#187

Closed
Octachron wants to merge 4 commits intoocaml:trunkfrom
Octachron:pattern_open
Closed

Local open in patterns#187
Octachron wants to merge 4 commits intoocaml:trunkfrom
Octachron:pattern_open

Conversation

@Octachron
Copy link
Member

This pull request adds support for local open inside patterns. Four new constructions mirroring the local open constructions for expressions are added

  • M.(pattern)
  • M.[pattern_list]M.([pattern_list])
  • M.{labeled_pattern_list}M.({labeled_pattern_list})
  • M.[| .. |]M.( [| .. |] )

At the typing phase, the construction M.(pattern) brings all identifiers defined within M inside the scope and then proceed with the typing of pattern. All others expression are desugared to the M.(..) construction during parsing.

One the main advantages of these constructions would be to deconstruct record with the same
syntax used to construct them

let f M.{x} = M.{x}

Other uses of these local pattern might be quite unfrequent but the similarity with the local open for expression should help users to discover or remember this feature when the need arises. Since there is no analogous to let open M in ... on the pattern side, local open in pattern and expression are not completely symmetric. However, let open M in .. constructions in the pattern side would look quite foreign to me.

Questionable implementation details:

  • Currently, the local pattern open use the type_open function like the local expression pattern. However, this implies that values defined inside M are also brought to the scope. Does this warrant a specialised type_open_for_pattern function?
  • This pull request does not provide support for M.() and M.[] whose semantics seem quite ambiguous to me.

@alainfrisch
Copy link
Contributor

However, this implies that values defined inside M are also brought to the scope.

It's useless to do so, but I don't see why this would be problematic. Do you see such a reason?

@Octachron
Copy link
Member Author

No, as far as I can see, bringing values to the scope should not create any problem. It just feels non optimal to add values in the environment which are never used.

@alainfrisch
Copy link
Contributor

@alainfrisch
Copy link
Contributor

I'm not familiar with the type-checking of patterns, and in particular about why an "Env.t ref" is passed (and not an Env.t) in type_pat. The new case for Tpat_open loose sharing these references. Does it matter?

@Octachron
Copy link
Member Author

It does matter. In presence of gadt constructor, the env reference is used as a side-channel to share type equalities discovered during type unification. So the current code breaks gadt typing in pattern!

Thanks for pointing this stupid mistake, I will correct it soon.

@Octachron
Copy link
Member Author

I updated the pull request to fix the problem when mixing gadt and pattern open.

The new commit fix this problem by controlling more finely which information is shared between environment during pattern typing:

  • GADT type equalities need to be shared with the "outside" environment
  • Types, constructors (and values) introduced in a pattern open should not be shared.

To solve these constraints, I have added an environment stack in type_pat. When entering a pattern open, the signature of the associated module is added to a copy of the current environment. This new env is then pushed on the environment stack and poped when exiting the pattern open. Environment changes during pattern typing are then propagated to the whole stack.

A major caveat of this approach is that type equalities are recomputed at each level of the stack. Fortunately, it should mainly affect patterns with gadt constructors deeply nested inside pattern open. It seems sensible to conjecture, for now, that these patterns will be uncommon. If this cause performance problems in the future, it should be possible to optimise this behavior. However, I think that this optimisation would probably need more invasive changes, whereas the current code does not alter pattern typing in absence of pattern open.

  • The test suite have been updated to better detect problems with pattern open and gadt. However, it is still probably incomplete. I will fix this point later.

@alainfrisch
Copy link
Contributor

Jacques Garrigue is working on a branch ( http://caml.inria.fr/cgi-bin/viewvc.cgi/ocaml/branches/gadt-warnings/ ) with a much revised version of pattern type-checking (for better support of GADTs). It would be useful to synchronize with him on this topic.

I'm not so fond of the stack approach. It seems the environment is used for two different purposes (collecting equalities, resolving lookups); isn't there a way to separate these uses and keep only two environments, one for each use?

@Octachron
Copy link
Member Author

The stack solution is clearly hackish. A better long-term solution might be to split the environment between a shared part and an immutable part during type unification. Splitting the environment might also slightly facilitate the environment backtracking in the gadt-warning branch. I would have a look at this idea this week.

@Octachron
Copy link
Member Author

I have added a new version which does not use an environment stack when typing a local open in pattern.

To achieve this goal, I have added an Env.mut type which splits the environment into a local and shared components. During type unification or pattern typing, the shared environment is used to share type equalities, whereas the local environment is used in local pattern open to locally pull in scope the components of the opened module. When the partial sharing is not needed anymore, the two parts of the environment are merged back together in a standard Env.t.

I have also added an id field to Env.t to replace the physical equality check in Ctype.check_abbrev_env which was unruly in presence of shared mutable environment. This new id is essentially here to track changes in the environment and to ensure that the transformation Env.(freeze @@ thaw x) is correctly identified as the identity transformation.

@gasche
Copy link
Member

gasche commented Aug 22, 2015

(For the record, I asked Jacques for an opinion on the patch, but he needs more time for a review.)

@Octachron Octachron force-pushed the pattern_open branch 2 times, most recently from 524e590 to b9d8340 Compare October 26, 2015 15:47
@Octachron
Copy link
Member Author

Since the gadt-warning branch have been merged back to trunk, I have rebased this pull request to solve the resulting merge conflicts. Most of the changes were quite trivial. The only exception might be the function Typecore.save_state which now saves only the mutable part of the environment rather than the full environment.

After some hesitations, I have also added the syntactic sugar for M.() and M.[] to better mirror the local open for expressions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity: what does arl stand for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stood for array_record_list. A better name might be simple_delimited_pattern or maybe simply
simple_pattern_array_record_list.

@damiendoligez
Copy link
Member

@garrigue do you have time to review this patch?

@Octachron Could you add an entry in Changes?

@Octachron
Copy link
Member Author

I have added a change entry in Changes and updated the local open section in the manual.

I have also cleaned a little bit the code: comments have been somewhat improved and the french-influenced envf was replaced by the more explicit frozen_env.

Were the pull request accepted, I would clean the history to clear up the wandering between implementations.

(side-note: parallel builds of world.opt have been broken once again due to a desynchronized .depend file )

@garrigue
Copy link
Contributor

garrigue commented Feb 2, 2016

Sorry, I won't have time for this until next week.

@damiendoligez damiendoligez modified the milestones: 4.04-or-later, maybe-4.03.0 Feb 2, 2016
During type unification and pattern typing in presence of GADTs, it is
useful to be able to share type equalities. Before this commit, this
was done by using a environment reference to modify the environment
when needed.

However, that made impossible to locally update the environment without
either losing the shared reference or propagating globally the changes.
This is problematic for local opens in pattern that need to introduce
new identifiers in the environment for the sake or name lookup but only
within a limited scope. This commit introduces an `Env.mut` type to fix
this limitation in preparation of local open in pattern.
Octachron added 3 commits May 5, 2016 21:34
This commits extends the pattern syntax to support local open in
patterns. Four new constructions mirroring the expression constructions
are added

    * `M.(pattern)`
    * `M.[pattern_list]` ⟺ M.([pattern_list])
    * `M.{labeled_pattern_list}` ⟺ M.({label_pattern_list})
    * `M.[| .. |] ⟺ M.( [| .. |] )

At the typing phase, the construction `M.(pattern)` brings all
identifiers defined within M inside the scope and then proceed with the
typing of `pattern`. All others constructions are desugared to the
`M.(..)` construction during parsing.

Questionable implementation details:
* Currently, the local pattern open use the `type_open` function like
the local expression pattern. However, this implies that values defined
inside `M` are also brought to the scope. A specialized
`type_open_for_pattern` would be more efficient.
@Octachron
Copy link
Member Author

Some minor changes: I have cleaned the PR history and rebased onto trunk. I have also simplified a little the code by removing the environment id change.

@garrigue
Copy link
Contributor

garrigue commented May 9, 2016

After reading the code, I have no problem with the basic idea, but the freeze/thaw seems an overkill.
Actually, there is no real need to merge environments (and doing just append is not correct for gadt_instances).
My idea would be to split the local constraints from the types in the evironment, so that one would only have to copy them over when leaving the scope of an open. This should make more sense in the long run.
I'm going to propose my own version of the patch, avoiding changes in Ctype.

@garrigue
Copy link
Contributor

garrigue commented May 9, 2016

I have opened a new pull request #578 for my branch of this patch.
The idea there is to thread the local information by hand, rather than not-so-well understood merge.

@Octachron
Copy link
Member Author

Thanks for the review and the code change. Threading just the local type information looks indeed much better. I am closing this PR in favor of #578.

@Octachron Octachron closed this May 9, 2016
@gasche
Copy link
Member

gasche commented May 9, 2016

I rebased the two patchsets together and merged in trunk ( 2d33e16 ). Thanks for the work!

gasche added a commit that referenced this pull request Aug 3, 2016
shindere pushed a commit to shindere/ocaml that referenced this pull request Aug 11, 2016
camlspotter pushed a commit to camlspotter/ocaml that referenced this pull request Oct 17, 2017
mshinwell pushed a commit to mshinwell/ocaml that referenced this pull request Jun 26, 2020
lthls added a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls added a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls added a commit to lthls/ocaml that referenced this pull request Sep 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants