free-foil-0.4.0: Efficient Type-Safe Capture-Avoiding Substitution for Free (Scoped Monads)
Safe HaskellNone
LanguageHaskell2010

Control.Monad.Foil.Registry

Description

Deterministic stripe assignment for separately checked units.

Each unit of a module system allocates its top-level names inside its own reservation (see Control.Monad.Foil.Blocks), and the assignment of reservations has to be deterministic: a unit's declarations are numbered base, base + 1, and so on in declaration order, whatever else is checked around it. Determinism is what makes raw names cacheable, since a unit checked today and a unit loaded tomorrow then agree name for name. It is also what discharges the trust obligation of checkExtScope.

The registry is that assignment: an append-only map from unit names to stripe indices, handing out the next index on first use. A build persists it beside the build products, since a cached artifact survives a change elsewhere in the build exactly when the assignment does not move.

Where the stripes lie on the raw-name line is a StripeLayout and a policy of the client. The library is region-agnostic, and the allocator admits negative names.

Synopsis

Stripe indices

newtype StripeIndex Source #

A stripe's position in the registry: which run of names a unit draws from. Its own type, so that a stripe index cannot be confused with a name, a count, or an offset.

Since: 0.4.0

Constructors

StripeIndex Int 

Layouts

newtype StripeSize Source #

How many names a unit may declare: the width of every stripe a layout hands out. Its own type, so that a size cannot be confused with a name, an index, or a base.

Since: 0.4.0

Constructors

StripeSize Int 

newtype StripeLayout Source #

Where stripe i lies on the raw-name line.

The library does not choose: whether stripes descend below zero, ascend from some base, or interleave with other reservations is a policy of the client, and everything in Control.Monad.Foil.Blocks works from the resulting NameRanges alone. A layout should give disjoint ranges to distinct indices. Nothing checks this here, but withDisjointUnion refuses the overlap at the point where it would do harm.

Since: 0.4.0

Constructors

StripeLayout 

stripesBelowZero :: StripeSize -> StripeLayout Source #

Stripe i is the i-th run of size names below zero, counting downwards, so stripe 0 is [-size .. -1]. Within a stripe, allocation still ascends (see withFreshIn), so declaration order is ascending name order.

This layout leaves the whole non-negative range free for a client's local names.

>>> stripeRange (stripesBelowZero (StripeSize 100)) (StripeIndex 0)
NameRange {nameRangeLo = -100, nameRangeHi = -1}
>>> stripeRange (stripesBelowZero (StripeSize 100)) (StripeIndex 2)
NameRange {nameRangeLo = -300, nameRangeHi = -201}

Since: 0.4.0

stripesAbove Source #

Arguments

:: RawName

The base: the low end of stripe 0.

-> StripeSize 
-> StripeLayout 

Stripe i is the i-th run of size names at or above a base, counting upwards, so stripe 0 is [base .. base + size - 1].

>>> stripeRange (stripesAbove 0 (StripeSize 100)) (StripeIndex 1)
NameRange {nameRangeLo = 100, nameRangeHi = 199}

Since: 0.4.0

Local-region layouts

newtype RegionWidth Source #

How far apart consecutive local-region floors sit within a unit's runs. This is spacing, not a hard width: a run is open-ended above its floor, and a scope-driven allocator would have to hold this many names /in scope at once/ to reach the next floor.

Since: 0.4.0

Constructors

RegionWidth Int 

newtype RegionsPerUnit Source #

How many runs of local names a unit may hold before its runs would spill into the next unit's. A spill is not unsound for a client that refreshes on clash. It only forfeits the disjointness described under RegionLayout for the runs past the cap.

Since: 0.4.0

Constructors

RegionsPerUnit Int 

data RegionLayout Source #

Where a unit's runs of local names lie: one open-ended region per declaration (or command) of the unit, advanced with nextRegion as the unit's declarations are processed.

Stripes make a unit's top-level names disjoint from every other unit's, and runs of local regions do the same for the names a checker invents inside a declaration. A term stored under one declaration then never collides with another declaration's live locals when it is reopened, so a refreshing substitution takes its no-rename fast path throughout.

The first run is derived from the unit's stripe index rather than from a counter shared across units, so a unit's elaboration depends only on the unit itself and editing a neighbour moves no name. That is the determinism a cache rests on.

The trade-off is that local names carry large offsets. A client that shows raw indices directly may prefer a single flat region, and accept the transient renames instead.

Since: 0.4.0

Constructors

RegionLayout 

Fields

regionsAbove :: RawName -> RegionsPerUnit -> RegionWidth -> RegionLayout Source #

Runs ascending from a base: the unit with stripe index i starts its runs at base + i * perUnit * width, and each declaration's floor sits width above the previous one. The top of every run is open.

>>> let locals = regionsAbove 0 (RegionsPerUnit 0x10) (RegionWidth 0x100)
>>> nameRangeLo (firstRegionOf locals (StripeIndex 2))
8192
>>> nameRangeLo (nextRegion locals (firstRegionOf locals (StripeIndex 2)))
8448

Since: 0.4.0

The registry

type Registry name = Map name StripeIndex Source #

Which stripe each unit's declarations live in, by the unit's name.

Append-only: a name, once registered, keeps its stripe for the lifetime of the registry, and the next stripe index is always the registry's size.

Since: 0.4.0

emptyRegistry :: Registry name Source #

The registry before any unit has ever been checked.

Since: 0.4.0

registrySize :: Registry name -> Int Source #

How many units have been registered, which is also the next free stripe.

Since: 0.4.0

registerUnit :: Ord name => name -> Registry name -> (Registry name, StripeIndex) Source #

The stripe index of a unit, assigning the next one on first use.

Registration hands out the index and not a range. A unit's index determines every reservation derived for it: its stripe under a StripeLayout, and its runs of local names under a RegionLayout. The layouts interpret the index, rather than being consulted here.

>>> let layout = stripesBelowZero (StripeSize 10)
>>> let (r1, iA) = registerUnit "A" emptyRegistry
>>> stripeRange layout iA
NameRange {nameRangeLo = -10, nameRangeHi = -1}
>>> stripeRange layout (snd (registerUnit "B" r1))
NameRange {nameRangeLo = -20, nameRangeHi = -11}

Registration is idempotent, which is the determinism a cache rests on:

>>> snd (registerUnit "A" r1) == iA
True

Since: 0.4.0