[WIP] Proc System: A Separate World for Controlled Mutation
Phox separates pure and procedural worlds at the type and syntax level.
Procedural types
Procedural types (DynArray! a, Slice! a, Ptr! a, …) represent mutable data structures used only inside procedural blocks.
- cannot escape into the pure world
- pure functions cannot observe or depend on them
- they exist only as temporary mutable views created via
thaw! - they must be converted back to pure values via
freeze!
Procedural types are always local to a single VM instance and never shared.
proc! {
let buf = thaw!(xs); // @[a] → DynArray a
do_inplace_operation!(buf);
freeze!(buf) // DynArray a → @[a]
}
User-defined procedures
Define procedures:
- Procedure name must end with
! - Procedures are uncurried
proc(args..) {...}is procedure abstraction
*let foo! = proc(x) {...};
*let bar! = proc(x,y) {...};
*let download! = proc(url) {...};
Procedure call (only allowed inside proc! { ... }):
proc! {
foo!(1);
bar!(2,3);
() // `proc! {...}` must return a **pure** value
};
let x = proc! { download!(url) };
Resource types
Resource types represent opaque handles to external OS resources.
- can escape into the pure world
- pure functions cannot observe or pattern-match them
- operations on resource values are allowed only in proc world
- each resource type defines a destructor
drop! drop!is called automatically when the reference count becomes zerodrop!cannot be called explicitly
Resource values may be shared within a single VM instance without mutual exclusion,
because procedural types never escape and pure values are immutable.
Note
Resource types are builtin and provided only by the runtime system.
Users cannot define new resource types.// ---- // The following `resource ... drop! ...` syntax is **illustrative only** // and does not exist in the actual language: // ---- // Define resource type. // - Resource name must end with `!` // - Resource type has exactly one constructor of the same name // - Resource type has exactly one destructor `drop!` // - `drop!` is called automatically when Rc becomes 0 // - Construction and pattern match are allowed only in proc world resource MyResource! a = @[a] drop! = proc(MyResource! xs) { ... };
Concurrency and VM instances
Each VM instance is a single-threaded execution context.
- procedural types never escape
→ no shared mutable state - pure values are immutable
→ safe to share - resource values are opaque
→ safe to share as long as operations are restricted to proc world
and isolated within VM boundaries
Only the await operation can transfer resource values between VM instances.
This means:
- resource sharing/movement happens only at
awaitboundaries - mutual exclusion is required only for resource operations that cross VM boundaries
- no mutual exclusion is needed inside a single VM instance
If the job’s return value does not contain a resource value,
there is no limit on the number of waiters.
Otherwise, Phox limits the number of waiters for a JobHandle a to 1 at most.
In this case, await job consumes the resource returned by job (ownership
transfer), and any subsequent calls to await job will result in an error.
Note
Resource operations may interact with external OS resources.
External resources are not pure and may cause race conditions.
Phox guarantees safety inside the VM, but external resource conflicts
must be handled by appropriate OS-level APIs (e.g., file locks).
Opacity of Resources and Transparency of Resource Ownership
Resource-type values are opaque.
However, resource ownership must be structurally visible and transparent.
To prevent resource leaks,
Phox restricts the encapsulation of resource values within opaque structures.
Specifically:
- Closures cannot cross the VM boundary.
- Any values containing closures cannot cross the VM boundary.
Therefore, await job can return the following:
- ADTs, Arrays, tuples, or records that do not contain closures,
- Resource values, or
- Primitive values.
Rules for Transparency of Resource Ownership
-
Resource Inflow Violation Rules:
- Values bound by top-level
let/let recmust be resource-free - Values passed to a
taskconstructor as its arguments must be resource-free.
These values will be bound to the initial environment of the corresponding job. - The initial environment of a job contains the task’s arguments only.
(resource-free environment) - And job can access to the top-level/global environment.
(resource-free environment)
- Values bound by top-level
-
Resource Outflow Violation Rules:
- The return value of
await jobmust be resource-transparent
- The return value of
-
Resource Sourcing Violation Rule:
- The return value of
proc!{...}must be resource-transparent
if such expressions exist in top-levellet/let recbindings.
- The return value of
where:
-
- resource-free means
- The value must not contain any resource values
-
- resource-transparent means
- The value must not contain any opaque structures, such as closures
(This prevents resources from being hidden inside ADTs or closures.)
Note
In other words,
- Top-level
let/let recbindings must be resource-free:
their right-hand-side expressions (and all subexpressions) must not construct resource values.- A call to the
taskconstructor must be resource-free.
The expression passed as its argument (and all its sub-expressions) must not contain any resource values nor opaque structures.- The return value of
await job(i.e. the resulting value of atask) must be resource-transparent.
Open issues
Note
TODO: Phox must detect and eliminate cases where top-level
let/let recbindings contain resource values by recursively checking the AST.The below is the typical case:
// `r` is a resource value.
let r = proc!{ open_file!("foo.txt") };
// λ expression that captures resource `r`.
let f = \x. proc! { write!(r, x); };
// Note that value structure of type `MyADT a` is opaque for the type system.(!)
// ADT values can encapsulate closures. (resource `r` leaks!)
type MyADT a = MyADT (a -> ());
let v = MyADT f;
Note
T.B.D.: Phox may restrict use of
proc! {...}only for*lettemplate definitions.
This can eliminate most miss-usecases like the above in the language syntax-level.
See also Structural Transparency of Types (STraT).