... | ... | @@ -547,218 +547,4 @@ pattern Pat { a } = Just a |
|
|
## Associating synonyms with types
|
|
|
|
|
|
|
|
|
This section is based upon [\#10653](https://gitlab.haskell.org//ghc/ghc/issues/10653).
|
|
|
|
|
|
|
|
|
Pattern synonyms allow for constructors to be defined, exported and imported separately from the types which they build.
|
|
|
However, it is sometimes convenient to associate synonyms with types so that we can more closely model ordinary data constructors.
|
|
|
|
|
|
|
|
|
If we want to refactor to change the internal representation of this maybe-like type to use Maybe.
|
|
|
|
|
|
```wiki
|
|
|
-- Main.hs
|
|
|
module Main where
|
|
|
|
|
|
import Internal ( A(..))
|
|
|
|
|
|
-- Internal.hs
|
|
|
module Internal where
|
|
|
|
|
|
data A = MkA Int | NoA
|
|
|
```
|
|
|
|
|
|
|
|
|
If we modify `Internal.hs` as follows
|
|
|
|
|
|
```wiki
|
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
|
module Internal where
|
|
|
|
|
|
newtype A = NewA (Just Int)
|
|
|
|
|
|
pattern MkA n = A (Just n)
|
|
|
|
|
|
pattern NoA = A Nothing
|
|
|
```
|
|
|
|
|
|
|
|
|
Then local definitions to `Internal` which used `A` would work as before but modules importing `Internal` and `A`
|
|
|
will no longer work as importing `A(..)` will import the type `A` and the constructor `NewA`. We can explicitly import the
|
|
|
new patterns but the usage of pattern synonyms should be transparent to the end user. What's needed is to be able to
|
|
|
associate the new synonyms with a type such that client code is oblivious to this implementation.
|
|
|
|
|
|
### Proposal
|
|
|
|
|
|
|
|
|
Richard proposes that synonyms are associated at the export of a datatype. Our running example would then look as follows:
|
|
|
|
|
|
```wiki
|
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
|
module Internal(A(MkA, NoA)) where
|
|
|
|
|
|
newtype A = NewA (Just Int)
|
|
|
|
|
|
pattern MkA n = A (Just n)
|
|
|
|
|
|
pattern NoA = A Nothing
|
|
|
```
|
|
|
|
|
|
### Specification
|
|
|
|
|
|
|
|
|
This proposal only changes module imports and exports.
|
|
|
|
|
|
#### Definition
|
|
|
|
|
|
|
|
|
We say that "a pattern synonym `P` is associated with a type `T` relative to module `M`" if and only if "`M` exports `T` whilst associating `P`".
|
|
|
|
|
|
#### Exports
|
|
|
|
|
|
|
|
|
For any modules `M``N`, we say that "`M` exports `T` whilst associating `P`" just when
|
|
|
|
|
|
- The export has the form `T(c1, ..., cn, P)` where c1 to cn (n \>= 0) are a mixture of other field names, constructors, pattern synonyms and the special token `..`. The special token `..`, which indicates either
|
|
|
|
|
|
1. all constructors and field names from `T`'s declaration, if `T` is declared in this module; or
|
|
|
1. all symbols imported with `T`, which might perhaps include patterns associated with `T` in some other module.
|
|
|
|
|
|
|
|
|
In case (2), `..` might in fact be a union of sets if `T` is imported from multiple modules with different sets of associated definitions.
|
|
|
|
|
|
#### Imports
|
|
|
|
|
|
|
|
|
For any modules `M``N`, if we import `N` from `M`,
|
|
|
|
|
|
- The abbreviated form `T(..)` brings into scope all the constructors, methods or field names exported by `N` as well any patterns associated with `T` relative to `N`.
|
|
|
- The explicit form `T(c1,...,cn)` can name any constructors, methods or field names exported by `N` as well as any patterns associated with `T` relative to `N`.
|
|
|
|
|
|
#### Typing
|
|
|
|
|
|
|
|
|
WIP
|
|
|
|
|
|
#### Clarification
|
|
|
|
|
|
- Associated patterns are typechecked to ensure that their type matches the type they are associated with.
|
|
|
|
|
|
- Hence, all synonyms must be initially explicitly associated but a module which imports an associated synonym is oblivious to whether they import a synonym or a constructor.
|
|
|
|
|
|
- According to this proposal, only pattern synonyms may be associated with a datatype. But it would be trivial to expand this proposal to allow arbitrary associations.
|
|
|
|
|
|
#### Examples
|
|
|
|
|
|
```
|
|
|
moduleN(T(..,P))wheredataT=MkTIntpatternP=MkT5-- M.hsmoduleMwhereimportN(T(..))
|
|
|
```
|
|
|
|
|
|
`P` is associated with `T` relative to `N`. M imports `T`, `MkT` and `P`.
|
|
|
|
|
|
```
|
|
|
moduleN(T(..))wheredataT=MkTIntpatternP=MkT5-- M.hsmoduleMwhereimportN(T(..))
|
|
|
```
|
|
|
|
|
|
`P` is unassociated. `M` imports `T` and `MkT`.
|
|
|
|
|
|
```
|
|
|
moduleN(T(P))wheredataT=MkTIntpatternP=MkT5-- M.hsmoduleMwhereimportN(T(..))
|
|
|
```
|
|
|
|
|
|
`P` is associated with `T` relative to `N`. M imports `T`, and `P`.
|
|
|
|
|
|
```
|
|
|
moduleN(T(P))wheredataT=MkTIntpatternP=MkT5-- M.hsmoduleM(T(..))whereimportN(T(..))-- O.hsmoduleOwhereimportM(T(..))
|
|
|
```
|
|
|
|
|
|
`P` is associated with `T` relative to `N`.
|
|
|
|
|
|
|
|
|
As `M` imports `N` and imports `T`, `P` is associated with `T` relative to `M`. Thus `M` exports `T` and `P`.
|
|
|
|
|
|
|
|
|
Therefore when `O` imports `T(..)` from `M`, it imports `T` and `P`.
|
|
|
|
|
|
```
|
|
|
moduleN(T(..))wheredataT=MkTInt-- M.hsmoduleM(T(P))whereimportN(T(..))patternP=MkT5-- O.hsmoduleOwhereimportM(T(..))
|
|
|
```
|
|
|
|
|
|
|
|
|
This example highlights being able to freely reassociate synonyms.
|
|
|
|
|
|
`M` imports `T` and `MkT` from `N` but then as `M` associates `P` with `T`, when `O` imports `M`, `T` and `P` are brought into scope.
|
|
|
|
|
|
### Unnatural Association
|
|
|
|
|
|
|
|
|
There is some discussion about what should happen with synonyms which target types defined in the prelude.
|
|
|
It is very uncommon to explicitly import datatypes defined in the prelude, thus this kind of association is very rare in practice
|
|
|
but should be allowed.
|
|
|
|
|
|
### Module Chasing
|
|
|
|
|
|
|
|
|
Simon is a bit worried that once we allow this association then there is no limit to the number of constructors which
|
|
|
can be associated with a type. With normal datatypes, `T(..)` means some subset of the constructors defined where `T` is
|
|
|
defined. With this proposal `T(..)` has no maximal meaning, I don't see any problem with this behaviour as the meaning can still
|
|
|
be determined by the renamer.
|
|
|
|
|
|
### The Privilege Objection
|
|
|
|
|
|
|
|
|
Simon also wonders why it is possible to privilege pattern synonyms in this way and not normal functions for example.
|
|
|
I don't think this is particularly puzzling as at their most general pattern synonyms allow for the definition of unassociated
|
|
|
data constructors. Being able to later associate them with types only brings their behaviour closer to ordinary data constructors.
|
|
|
|
|
|
### Polymorphic Synonyms
|
|
|
|
|
|
|
|
|
The following is a valid pattern synonym declaration which doesn't have a definite constructor.
|
|
|
|
|
|
```wiki
|
|
|
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
|
|
|
module Foo where
|
|
|
|
|
|
class C f where
|
|
|
build :: a -> f a
|
|
|
destruct :: f a -> a
|
|
|
|
|
|
pattern P :: () => C f => a -> f a
|
|
|
pattern P x <- (destruct -> x)
|
|
|
where
|
|
|
P x = build x
|
|
|
```
|
|
|
|
|
|
|
|
|
I propose that we allow such synonyms to be associated with a type `T` as long as it typechecks. I don't expect this to be much used in practice.
|
|
|
|
|
|
### Associatation at definition
|
|
|
|
|
|
|
|
|
Simon proposed that synonyms are associated at the definition of a datatype. Our running example would look as follows:
|
|
|
|
|
|
```wiki
|
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
|
module Internal(A(MkA, NoA)) where
|
|
|
|
|
|
newtype A = NewA (Just Int)
|
|
|
with (MkA, NoA)
|
|
|
|
|
|
pattern MkA n = A (Just n)
|
|
|
|
|
|
pattern NoA = A Nothing
|
|
|
```
|
|
|
|
|
|
|
|
|
The proposal refers to Richard's suggestion rather than Simon's refinement for the following reasons.
|
|
|
|
|
|
|
|
|
Consider two packages `old-rep` and `new-rep` which have different representations of the same structure.
|
|
|
The library author wants to smooth the transition for his users by providing a compatibility package `compat-rep`
|
|
|
so that code using the old representation in `old-rep` can work seamlessly with `new-rep`.
|
|
|
|
|
|
|
|
|
The problem is to define `compat-rep` such that by changing the dependencies of our package, our code to continues to work
|
|
|
but without depending on `old-rep`. More generally, an author may want to write a `*-compat` package for two packages which they do
|
|
|
not control. Having to define these synonyms at the definition site is too restrictive for this case . |
|
|
See [PatternSynonyms/AssociatingSynonyms](pattern-synonyms/associating-synonyms) |
|
|
\ No newline at end of file |