Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Classifying capabilities#23463

odersky started this conversation inCC experiment
Jul 2, 2025· 8 comments· 1 reply
Discussion options

The motivation for this is to come up with a type forTry.apply orFuture.apply that reflects the capability structure.Try.apply would have a type like this one:

objectTry:defapply[T](body:=>T):Try[T]^{???}

The??? should capture all control capabilities ofbody (such asLabels orCanThrows). After theapply exits, the capabilities owned by body are spent, except for those control capabilities. These are retained in the failure part of aTry since they will be re-thrown when theTry is accessed by aget or a pattern match.

The problem is we are lacking a way to express those control capability slices, nor do we have a way to reason about them (i.e. know which ones subsume which other ones, or which ones can be discarded).

The idea is to add a new kind of capability acting as a filter. Ifc is a capability, thenc.only[C] would stand for all capabilities implied byc that have a type deriving from classC. Specialized to control, we could introduce a new trait

traitControlextendsClassifierCapability

that is a base class of control capability classes such asLabel,CanThrow, orAsync. With that structure in place, we could give the following signature toTry.apply:

objectTry:defapply[T](body:=>T):Try[T]^{body.only[Control]}

c.only[C] is called afiltered capability. Generally,c.only[C] is a new capability qualifier, alongsidec.rd andc*. If multiple qualifiers are given then.only comes after* but before.rd, i.e.c*.only[C].rd would be in the correct order. There can only be a singleonly-qualifier on a capability.

We now need to develop subsumption rules for filtered-capabilities. We clearly should have

    c.only[C]  <:  c    c1.only[C1]  <:  c2.only[C2]       if c1 <: c2, C1 <: C2

What about the other combinations? When isc <: c.only[C]? We do want this to hold sometimes. For instance ifasync: Async whereAsync derives fromControl then it would be nice to be able to conclude thatasync <: async.only[Control].

Another issue is how to detect disjointness. For instance, a particular operationop might capture a Labellbl and a mutable matrixm.

op: ()->{lbl, m}Unit

What is the type ofTry.apply(op())? Consulting the signature ofTry.apply we get

Try[Unit]^{op.only[Control]}

Replacingop with the actual argument, we get{lbl.only[Control], m.only[Control]} as the capture set of the result.lbl.only[Control] should simplify tolbl by the reasoning we outlined before.m.only[Control] should ideally simplify to nothing, since a mutable matrix would not expected to capture a control capability. So, ideally

Try.apply(op()):Try[Unit]^{lbl}

To get there, we introduce classifier capability classes. These are classes (or traits) that declareClassifierCapability as a direct parent class. In particular we would have:

traitSharedCapabilityextendsClassifierCapabilitytraitControlextendsClassifierCapability,SharedCapabilitytraitMutableextendsClassifierCapability

A class can not directly or indirectly extend two unrelated classifier capability classes.

Now, looking at subsumption rules, we first need this one:

     c <: c.only[D]

ifD is a classifier capability class andc is classified asD.

Definition
A capabilitycis classified as a classifier capability classD if the
transitive capture set ofc only contains capabilities that derive fromD.

Definition Thetransitive capture settcs(c) of an object capabilityc consists ofc itself plus the transitive capture sets of all capabilities captured by the type ofc. The transitive capture set of a root capabilityc is just{c}. The transitive
capture set ofc.rd is{x.rd | x in tcs(c)}

Definition A capabilitycderives from a classifier classD if one of the following applies:

  • c is an object capability of typeT andT <: D (alternatively,T hasD in its parent classes),
  • c is a root capabilitycap which originated from a typeC^{cap, ...} whereC <: D.
  • c is a read capabilityc1.rd andc1 derives fromD.
  • c is a capture set variableX and all elements in the alias or upper bound ofX derive fromD.

To make this sound, we have to restrict possible values ofFreshCap capabilities. If aFreshCap starts life in the capture set of a classC that extends a classifier classD then theFreshCap instance can subsume only capabilities classified asD. So FreshCap instances now carry restrictions for disjointness (in separation checking), levels, as well as classifiers.

Example

   lbl <: lbl.only[Control]

because thetcs(lbl) = {lbl, cap} where thecap is a root capability associated withLabel which is a subclass of the classifier classControl.

Furthermore, we have

   {c.only[D]} <: {}

ifD is a classifier capability class andtcs(c) only contains capabilities that derive from classifier class unrelated toC.

So

   {m.only[Control]} <: {}

becausem is classified asMutable, which is unrelated toControl.

You must be logged in to vote

Replies: 8 comments 1 reply

Comment options

odersky
Jul 4, 2025
Maintainer Author

What this mans for the implementation

  • Syntax and parsing rules foronly-capabilities.
  • AClassifiedCapability base trait.
  • Enforce restriction that a class cannot inherit two unrelated capability classes.
  • A new capability class foronly-capabilities.
  • ImplementcaptureSetOfInfo foronly capabilities.
  • The empty capability isx.only[Nothing]. It should have emptycaptureSetOfInfo.
  • Well-formedness rules:only must refer to a classified capability class or toNothing.
  • Normalization rules:
    - it's*, then.only, then.rd,
    - multiple.only normalize to the smallest one if the classes are related,
    - multiple.only normalize to the empty capability if the classes are not related.
  • define transitive capture set and cache it in a capability. Thetcs does not exist if a capture set in the hierarchy is still an unsolved capture set variable.
  • Add aclassifier field toFreshCap andResultCap.
  • ModifycapToFresh andtoResultInResults so that the classifier field is correctly set.
  • Usingtcs, define when a capability is classified as a classifier class.
  • A FreshCap with a classifierC can subsume only capabilities that are classified asC.
  • Implement subsumption rules:
    -c.as[C] <: d ifc <: d orc.as[D] <: empty
    -c.as[C] <: d.as[D] ifc <: d andC derives fromD
    -c <: d.as[D] ifc <: d andc is classified asD
    -c.as[D] <: empty iftcs(c) consists of capabilities that all derive from classifier classes unrelated toD.
You must be logged in to vote
1 reply
@odersky
Comment options

oderskyJul 7, 2025
Maintainer Author

WIP PR is#23485

Comment options

odersky
Jul 4, 2025
Maintainer Author

This looks like a lot of machinery to typecheckTry andFuture. I there a simpler way? I have not found one yet. Can we ignore the problem? Then it seems we have to typeTry(body) asT^{body}, which would ruin separation checking because then we cannot tell thatbody no longer can change anything because it has executed.

You must be logged in to vote
0 replies
Comment options

odersky
Jul 4, 2025
Maintainer Author

One possible generalization would be to have besides.as[C] also.asNot[C], or, with a different syntax:.as[!C]. This could be useful for instance to characterize the use set of aTry(body). It would bebody.as[!Control] since all control capabilities have been caught by theTry.

You must be logged in to vote
0 replies
Comment options

odersky
Jul 4, 2025
Maintainer Author

One possible simplification is to specialize the treatment to control capabilites:

  • Instead of@classifier traits have just two subtraits ofCapability:Control andNonControl.
  • Instead ofc.as[C], havec.ctrl andc.nonctrl.
  • Specialize the remaining rules accordingly.

This would make sense if we can convince ourselves that the only capability masking operations we envision mask specifically control capabilities. Can we?

You must be logged in to vote
0 replies
Comment options

odersky
Jul 5, 2025
Maintainer Author

One observation:.as can also be used to implement a new kind of capture set constraint:

  [C<: {cap.as[Mutable]}]// all elements of the set must be transitively `Mutable`.  [C<: {cap.as[Sharable]}// all elements of the set must be transitively `Sharable`.  [C<: {cap.as[Control]}// all elements of the set must be transitively `Control` capabilties.
You must be logged in to vote
0 replies
Comment options

odersky
Jul 6, 2025
Maintainer Author

And it is convenient to restrict capabilities of function types. For instance, when doing separation checking we might want to accept only functions over sharable capabilities. This can now be expressed:

A->{cap.as[Sharable]}B

We could probably make this nicer by defining capture set aliases:

typesharable^= {cap.as[Sharable]}A->{sharable}B

@bracevac Do you think this would work?

Overall I am coming to believe that this is a fundamental addition to capture checking that solves multiple expressiveness problems we had so far. So I tend to think we should do a trial implementation.

You must be logged in to vote
0 replies
Comment options

odersky
Jul 6, 2025
Maintainer Author

Maybe we need a more expressive language for what can go into an.as[...]. I mentioned already complement. We could also allow unions. Maybe we could write.as[Mutable, IOCapability], or.except[Control, Mutable]

You must be logged in to vote
0 replies
Comment options

This is a mechanism that enables limiting what can be closed over up to a certain threshold, which is very useful.

Without it, the next best thing one could do in the current system is using some form of branding through a variable in context (like we tried with the sub-regions example), but it's quite awkward and I don't think it scales well.

Classifiers seem to be less ad hoc, and it'd be good if we allowed users to extend the classifier hierarchy. Ithink classifiers subsume the advanced use cases from the2nd-class values works. With finer-grainedclosure up to, we can emulate, e.g., Fig. 7 in the paper I linked:

@classifiertraitReadextendsCapability@classifiertraitReadWriteextendsCapabilitytraitFileextendsCapability:typeR<:ReadtypeRW<:ReadWritegivenr:Rgivenrw:RW// operations guarded by capability membersdefread(usingthis.R):Bytedefwrite(usingthis.RW)(b:Byte):UnitdefwithFile[U](block:File^=>U):U// unrestricted use of files & other capabilitiesdefparReduce[U](xs:Seq[U])(op:U->{cap.only[Read]}U):U// only Read-classified allowedwithFile("foo.txt"): f=>  parReduce(1 to1000): (a, b)=>    f.write(a)// error ReadWrite is excluded    a+ b+ f.read()// ok  f.write(0x42)// ok access unrestricted

We can understand the proposed forms in terms of set operations involving transitive capture closure,only being some kind of intersection (is there a connection to separation checking?),
andexcept as some kind of set difference, or intersection with a complement.

However, the.except form, as discussed offline, needs to be carefully restricted, especially if we permit user-defined extensions of the classifier hierarchy and separate compilation:

E.g.,

// File1.scala:// Here, the universe of classifiers consists of the standard ones: Shareable, Mutable, ControldefmkPure(f: ()->{cap.except[Shareable,Mutable,Control]Unit): ()->{}Unit=   f// everything except all the known classifiers means nothing, so we are "pure"

can be subverted if we extend the classifiers elsewhere:

// File2.scala@classifierSneakyextendsCapabilityvals:Sneaky= ...valfakePure: ()->Unit=File1.mkPure(()=> s.boom())// ok!, since {s}.except[Shareable,Mutable,Control] = {s}
You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Labels
None yet
2 participants
@odersky@bracevac

[8]ページ先頭

©2009-2025 Movatter.jp