You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Remove Immut constraint, leaning on the monads for safety instead.
The safe-names system has been working well. Now that we have some experience
with it we can reevaluate some of the choices. Specifically, we can ask where
the `Immut` constraint, which add a fair bit of clutter, is actually necessary.
It turns out that `Immut n` served three purposes:
(1) It gated access to `getEnv` within env-carrying monads
(2) It gated access to fresh binder creation, like `withFreshBinder`
(3) It allowed us to work with non-sinkable `DistinctAbs` in some places
Regarding (1), it turned out that we only used `getEnv` in order to immediately
pass it to `runEnvReader` or similar, doing a little dance with `liftImmut`
along the way. It's simpler to just use `liftEnvReader` instead. Instead of
gating `getEnv` with `Immut`, we can just write it as `unsafeGetEnv` and be
careful in the few places we need to use it.
It turns out that (2) is more conservative than necessary. A `Binder n l` isn't
injectable in `l` but it *is* injectable in `n`. So it should be legal to have a
`withFreshBinder` that doesn't require `Immut n`. We still have to make sure
that the `Distinct l` constraint doesn't leak back to the caller, but we can do
that by hiding the distinctness constraint in the monad, just like we hide the
scope/env. (However, this is not currently implemented! See below).
Finally, (3) became a non-issue when we stopped using `DistinctAbs`. We only
used `DistinctAbs` for performance reasons, to save needless refreshing. We
replaced it with a dynamic distinctness check to trigger the fast path when
possible.
On top of all that, `Immut` was already broken, with `liftImmut` not providing
the guarantees I'd originally thought (for example, it makes it very easy to
have both `Immut n` and `Mut n` at the same time!).
The reason to make this change now is that I want to add a bilevel version of
InplaceT that emits to two different levels. We can reason about `Mut n` for
each level, but `Immut` is harder.
There is still one vulnerability: if you reify the `Distinct n` constraint by
capturing it in a GADT, you can return it from functions like `withFreshBinder`,
but it will no longer be valid if you subsequently emit more names. We could
solve this by hiding `Distinct n` in the monad, just like we hide the scope/env.
But this would mean we couldn't use the pure `sink` anymore. We'd have to use
the monadic version, `sinkM` instead. But you have to be a bit devious to
capture `Distinct n` in a GADT (especially now that `DistinctAbs` isn't a thing
anymore). So maybe this is a reasonable convenience-security trade-off?
0 commit comments