Skip to content

Instantly share code, notes, and snippets.

@dabrahams
Created February 6, 2025 18:06
Show Gist options
  • Save dabrahams/24bdfc7da6bfc7ce57c4e8faaecc1ef6 to your computer and use it in GitHub Desktop.
Save dabrahams/24bdfc7da6bfc7ce57c4e8faaecc1ef6 to your computer and use it in GitHub Desktop.

Expanding Projections

An attempt to deal with the multiple dimensions of projection mutability in Hylo, particularly those raised by slicing.

Problem

Right now we have strict coupling between the binding of the receiver of a projection and the properties of what is projected:

accessor receiver restrictions projected value
let - let-bound
inout mutable inout-bound
sink last use (maybe before resurrection) rvalue
set n/a

If a sink accessor is used, the receiver is consumed. Otherwise it is bound per the accessor during the lifetime of the projected value.

This arrangement can't express some things we care about.

Projected RValue

Whenever a fully-ephemeral value (one with no remote parts) is projected, it currently appears as an lvalue via let or inout accessors, but it could also be exposed as a fully owned value for reading.

Swift calls this accessor get, and I propose using the same keyword in Hylo. A let accessor can be synthesized from a get accessor (and it probably makes sense to forbid writing both, as Swift does with get/_read).

In Hylo, rvalues are mutable, but given that projections expose notional parts, it's important that projected rvalues are not mutable, or it would be easy to appear to mutate a part of a thing with no effect. You can always make a var binding if you want to mutate them.

“Locally-mutable” projection of immutable value

When a slice is projected out of an immutable value, we want it to be mutable so we can pop elements off the front or back, while its elements are immutable. That in turn suggests the need for separate Slice and SliceWithMutableElements (associated, for full flexibility) types.

trait Collection<Element> {
  associatedtype Element
  associatedtype Slice: Collection<Element>
    where Slice.Position == Position

  subscript(_ Range<Index>): Slice { nonmutating inout }
}

trait MutableCollection<Element>: Collection<Element> {
  associatedtype Element
  associatedtype Position
  associatedtype SliceWithMutableElements: MutableCollection<Element>
    where SliceWithMutableElements.Position == Position

  subscript(_ Range<Index>): SliceWithMutableElements { inout }
}

Note:

  1. A new kind of accessor (strawman syntax: nonmutating inout) is needed to indicate a thing that cannot escape the projection, is mutable, but whose mutations do not affect the receiver of the subscript.
  2. There are no get or let accessors here at all. We want the subscript of Collection to be used when a immutable MutableCollection is sliced. This is at least unusual; Swift requires get or let (_read in Swift) access when there is inout. We could rename nonmutating inout mutable let to hide this weirdness, but then one can take an inout binding to the result of a mutable let access.
  3. A subscript overload is required because the slice types are distinct. It's not just a matter of extending Collection's subscript with a new kind of accessor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment