| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Control.Monad.Foil.Blocks
Description
Reserved name blocks, and linking of independently checked scopes.
Each unit of a module system allocates its names inside its own
reservation (a NameRange, via withFreshIn), so that units checked
independently can be linked afterwards without renaming. ExtWithin is
the evidence for that: scope l extends scope n only within a set of
reserved ranges. Note that the ranges bound the extension and not the
scope, so the names of n itself (typically, a unit's imports) may lie
anywhere.
Two units that extend a common scope within disjoint reservations have
disjoint extensions. withDisjointUnion links them by comparing the
reservations rather than the scopes, and hands the continuation the
extension evidence for both sides, a ScopeUnion witness that the result
is the union and nothing more, and the union's own evidence, so that a
linked unit is itself linkable. Evidence composes along a chain of units
with composeExtWithin. To link more than two units, or to re-attach a
unit loaded from a cache, rebuild the union scope and mint the evidence
again with checkExtScope and checkScopeUnion.
checkExtScope and checkScopeUnion are a trust boundary. They compare
raw names across independently built scopes, which is meaningful only
under a deterministic reservation policy. Everything else in this module
either tests what it claims or constructs it.
Synopsis
- data ExtWithin (n :: S) (l :: S)
- extWithinRanges :: forall (n :: S) (l :: S). ExtWithin n l -> [NameRange]
- extWithinRefl :: forall (n :: S). NameRange -> ExtWithin n n
- extWithinStep :: forall (l :: S) (l' :: S) (n :: S). NameBinder l l' -> ExtWithin n l -> Maybe (ExtWithin n l')
- composeExtWithin :: forall (n :: S) (m :: S) (l :: S). ExtWithin n m -> ExtWithin m l -> ExtWithin n l
- data Block (c :: S) (l :: S)
- beginBlock :: forall (c :: S). NameRange -> Block c c
- resumeBlock :: forall (c :: S) (l :: S). NameRange -> ExtWithin c l -> Maybe (Block c l)
- blockRange :: forall (c :: S) (l :: S). Block c l -> NameRange
- blockExt :: forall (c :: S) (l :: S). Block c l -> ExtWithin c l
- withFreshInBlock :: forall (l :: S) (c :: S) r. Distinct l => Block c l -> Scope l -> (forall (l' :: S). DExt l l' => NameBinder l l' -> Block c l' -> r) -> r
- withExtendScopeRange :: forall (c :: S) r. Distinct c => Scope c -> NameRange -> Int -> (forall (n :: S). DExt c n => Scope n -> NameBinderList c n -> ExtWithin c n -> r) -> Maybe r
- data ScopeUnion (n :: S) (m :: S) (k :: S)
- withDisjointUnion :: forall (c :: S) (n :: S) (m :: S) r. (Distinct n, Distinct m) => ExtWithin c n -> ExtWithin c m -> Scope n -> Scope m -> (forall (k :: S). (Ext n k, Ext m k, Ext c k, Distinct k) => Scope k -> ScopeUnion n m k -> ExtWithin c k -> r) -> Maybe r
- checkScopeUnion :: forall (n :: S) (m :: S) (k :: S). Scope n -> Scope m -> Scope k -> Maybe (ScopeUnion n m k)
- checkExtScope :: forall (n :: S) (l :: S). Scope n -> Scope l -> Maybe (ExtEvidence n l)
- unionNameMaps :: forall (n :: S) (m :: S) (k :: S) a. ScopeUnion n m k -> NameMap n a -> NameMap m a -> NameMap k a
Extension-within-a-range evidence
data ExtWithin (n :: S) (l :: S) Source #
Evidence that scope l extends scope n only within a set of reserved
ranges: every name of l that is not a name of n lies inside one of
them.
The evidence is built alongside allocation, with extWithinRefl at the
start of a unit and extWithinStep at each binder, and composes along a
chain of scopes with composeExtWithin. Its runtime content is the
ranges, sorted and disjoint.
Since: 0.4.0
extWithinRanges :: forall (n :: S) (l :: S). ExtWithin n l -> [NameRange] Source #
The reservations an ExtWithin is evidence about: sorted, disjoint,
adjacent ranges coalesced, empty ones dropped.
Since: 0.4.0
extWithinRefl :: forall (n :: S). NameRange -> ExtWithin n n Source #
A scope extends itself within any range: the extension is empty.
Note that this does not say the range is disjoint from the scope. It is
withExtendScopeRange that checks that, because it allocates blindly.
Since: 0.4.0
extWithinStep :: forall (l :: S) (l' :: S) (n :: S). NameBinder l l' -> ExtWithin n l -> Maybe (ExtWithin n l') Source #
Extend the evidence across one more binder, if its name lies inside one of the ranges. One membership test per range.
A binder allocated by withFreshIn at one of these ranges always passes.
A binder allocated elsewhere, by withFresh or withRefreshed, is
rejected with Nothing unless it happens to land inside them, so the
evidence cannot be extended past a name that escapes the reservations.
>>>let range = NameRange 100 199>>>withFreshIn range emptyScope (\b -> fmap extWithinRanges (extWithinStep b (extWithinRefl range)))Just [NameRange {nameRangeLo = 100, nameRangeHi = 199}]
Since: 0.4.0
composeExtWithin :: forall (n :: S) (m :: S) (l :: S). ExtWithin n m -> ExtWithin m l -> ExtWithin n l Source #
Compose evidence along a chain of scopes: if m extends n only within
one set of ranges and l extends m only within another, then l
extends n only within their union.
The bound is the union of the two sets and not their hull, so a reservation lying between them stays linkable. Adjacent ranges are coalesced, so a chain of units with consecutive stripes collapses back to a single range.
>>>extWithinRanges (composeExtWithin (extWithinRefl (NameRange 0 9)) (extWithinRefl (NameRange 30 39)))[NameRange {nameRangeLo = 0, nameRangeHi = 9},NameRange {nameRangeLo = 30, nameRangeHi = 39}]>>>extWithinRanges (composeExtWithin (extWithinRefl (NameRange 0 9)) (extWithinRefl (NameRange 10 19)))[NameRange {nameRangeLo = 0, nameRangeHi = 19}]
Since: 0.4.0
Blocks in use
data Block (c :: S) (l :: S) Source #
A reservation in use: the range fresh names are allocated from, paired
with the evidence that everything allocated since the base scope c lies
within the unit's ranges.
The allocation range is always among the evidence's ranges, so stepping
the evidence at a freshly allocated name cannot fail and withFreshInBlock
is total. The two components are not redundant: the evidence is a
normalised set bounding the whole extension, and once units are composed
the range to allocate from can no longer be read off it.
Since: 0.0.1
beginBlock :: forall (c :: S). NameRange -> Block c c Source #
Start a unit: no names allocated yet, so the evidence is trivial.
Since: 0.4.0
resumeBlock :: forall (c :: S) (l :: S). NameRange -> ExtWithin c l -> Maybe (Block c l) Source #
Resume allocating from a range once the evidence has grown past what a
Block tracked by itself, after composing in a loaded unit's evidence
with composeExtWithin. This is what lets an interactive unit keep
allocating in its own reservation over the enlarged scope.
The allocation range must lie inside one of the evidence's ranges. The
ranges are normalised, so covering is containment in a single one, and
Nothing says the range is not covered.
>>>let grown = composeExtWithin (extWithinRefl (NameRange 0 9)) (extWithinRefl (NameRange 10 19))>>>fmap blockRange (resumeBlock (NameRange 0 9) grown)Just (NameRange {nameRangeLo = 0, nameRangeHi = 9})>>>fmap blockRange (resumeBlock (NameRange 30 39) grown)Nothing
Since: 0.4.0
blockRange :: forall (c :: S) (l :: S). Block c l -> NameRange Source #
The range withFreshInBlock allocates from.
Since: 0.4.0
blockExt :: forall (c :: S) (l :: S). Block c l -> ExtWithin c l Source #
The evidence accumulated so far: what a finished unit hands to
withDisjointUnion, or to composeExtWithin for the next unit of a
chain.
Since: 0.4.0
Arguments
| :: forall (l :: S) (c :: S) r. Distinct l | |
| => Block c l | The block to allocate from. |
| -> Scope l | The ambient scope. |
| -> (forall (l' :: S). DExt l l' => NameBinder l l' -> Block c l' -> r) | |
| -> r |
Allocate a fresh name in the block's range, stepping the evidence in
the same motion. Fails with error only on an exhausted range, exactly
as withFreshIn does.
>>>withFreshInBlock (beginBlock (NameRange 7 9)) emptyScope (\b block -> (nameId (nameOf b), extWithinRanges (blockExt block)))(7,[NameRange {nameRangeLo = 7, nameRangeHi = 9}])
Since: 0.4.0
Bulk extension of a scope by a range
Arguments
| :: forall (c :: S) r. Distinct c | |
| => Scope c | The scope to extend (typically, a unit's imports). |
| -> NameRange | The unit's reservation. |
| -> Int | How many names to allocate. |
| -> (forall (n :: S). DExt c n => Scope n -> NameBinderList c n -> ExtWithin c n -> r) | |
| -> Maybe r |
Extend a scope with the first k names of a range, in one step.
This is the bulk form of a unit's allocation, for loading a cached unit
whose extension is known to be k consecutive names, or for pre-allocating
a unit's names before checking its bodies. The range part of the scope must
be empty, which is checked, so the extension is fresh by construction.
Nothing reports an occupied range, and also a range with fewer than k
names.
The continuation receives the extended scope, the binders in ascending
order (for extending a NameMap in the same step), and the ExtWithin
evidence. The scope extension is a dense fromRange, \(O(k/W)\).
>>>withExtendScopeRange emptyScope (NameRange 100 199) 3 (\_ binders _ -> rawNameBinderList binders)Just [100,101,102]
Since: 0.4.0
Linking
data ScopeUnion (n :: S) (m :: S) (k :: S) Source #
Evidence that scope k is precisely the union of scopes n and m:
every name of n and of m is a name of k, and nothing else is.
The extension constraints ( state only the first
half, since a strict superset of the union satisfies them too. The second
half is what totality of a merged Ext n k, Ext m k)NameMap rests on, so unionNameMaps
demands this witness. It comes from withDisjointUnion, which builds the
union, or from checkScopeUnion, which tests for it.
Since: 0.4.0
Arguments
| :: forall (c :: S) (n :: S) (m :: S) r. (Distinct n, Distinct m) | |
| => ExtWithin c n | Evidence for the first unit. |
| -> ExtWithin c m | Evidence for the second unit. |
| -> Scope n | The first unit's scope. |
| -> Scope m | The second unit's scope. |
| -> (forall (k :: S). (Ext n k, Ext m k, Ext c k, Distinct k) => Scope k -> ScopeUnion n m k -> ExtWithin c k -> r) | |
| -> Maybe r |
Link two scopes that extend a common scope c within their respective
reservations. The evidence check is one sweep over the two range sets;
the scope union is one union.
Nothing when the two range sets overlap. The test is soundness and not
an optimisation. The extensions n \ c and m \ c lie inside their
respective range sets, so their disjointness is what guarantees that no
raw name denotes two different variables in the union. The names the two
scopes share are exactly the names of c, identified rather than renamed
apart, which is what linking two units over a common import must do.
The continuation receives both extension facts at once, a ScopeUnion
witness (which unionNameMaps requires), and the union's own ExtWithin,
so that a linked unit is itself linkable and a whole build folds through
this one function. It also receives , which a caller cannot
derive on the spot.Ext c k
Since: 0.4.0
checkScopeUnion :: forall (n :: S) (m :: S) (k :: S). Scope n -> Scope m -> Scope k -> Maybe (ScopeUnion n m k) Source #
Test that a scope is precisely the union of two others, and produce the witness if so. \(O(n+m)\).
This is the union witness for the re-attachment path, where the union
scope was rebuilt rather than handed down by withDisjointUnion. Like
checkExtScope, it compares raw names across independently built scopes,
and is meaningful only under a deterministic reservation policy.
Since: 0.4.0
checkExtScope :: forall (n :: S) (l :: S). Scope n -> Scope l -> Maybe (ExtEvidence n l) Source #
Test that every name of one scope is a name of another, and mint the
extension evidence if so. \(O(n+m)\) (isSubsetOf).
This is a trust boundary. The test compares raw names, and raw names from independently built scopes need not mean the same variable. The type system tracks meaning through binders, and this function goes around it deliberately, to re-attach a scope built elsewhere: in an earlier run, in a cache, or in a parallel session. It is sound only under the external discipline that a raw name has one global meaning, which a deterministic reservation policy provides. Nothing here checks that discipline, and the caller's allocator is what has to.
Since: 0.4.0
unionNameMaps :: forall (n :: S) (m :: S) (k :: S) a. ScopeUnion n m k -> NameMap n a -> NameMap m a -> NameMap k a Source #
Union of two total maps into a map on the union of their scopes.
Left-biased, like union.
The witness is what makes the result total on k. The inputs are total on
n and m, and ScopeUnion says that k holds their names and no
others. (It also determines k, which an extension constraint alone would
leave open.)
What no witness can say is that the two maps agree on the names their scopes share. Linked units agree there when the shared part comes from the same checked imports, and the left bias then only ever chooses between equal entries.
Since: 0.4.0