Encode state transitions in types using linear types
tweag.io
tweag.io
Linear types nicely generalizes this so that we don't need RAII wrappers or whatever. Just produce values and pass them around, eventually to be consumed. It feels like this is a powerful language idea.
Just curious, could you expand on this? What makes it weaker? Is it that affine types allow either consuming or not consuming a type?
https://play.rust-lang.org/?gist=b875377faa6e22b74ba8661ee10...
That little DSL lets you make multi-layer burgers, but only where bacon and patties alternate, and there is a bun on the top.
How might that look if you tried to do this in, say a (not compatible with C) C-like language? I thought the ownership system (affine types) was required for the borrow checker to work.
They're not the same thing.
> C++ ctor/dtor pairs are a sort of primitive linear type system that forces exactly one (implicit) use for each instance created.
ctor/dtor are closer to what Rust provides which is an affine type system. Linear types must be used exactly once, affine types can be used at most once. You can initialise a C++ object and never use it, that would not be allowed in a linear type system.
In an affine type system, every variable is used at most once. But C++ instantiations for RAII are not destructed more than once. Each instantiation has exactly one destruction.
Aside from actually having it integrated to the type system, Rust does exactly that. RAII is not linear.
> But C++ instantiations for RAII are not destructed more than once.
That's also what happens in Rust, it's not a linear type system.
You might be interested in [0].
Affine logic rejects only the contraction rule [1]; unlike linear systems still has weakening [2].
[0]: https://en.wikipedia.org/wiki/Substructural_type_system
[1]: https://en.wikipedia.org/wiki/Idempotency_of_entailment
[2]: https://en.wikipedia.org/wiki/Monotonicity_of_entailment
You may never use a C++ object, but it will be destroyed (under normal circumstances).
Implicit behaviour is not use.
> You may never use a C++ object, but it will be destroyed (under normal circumstances).
And that is different from Rust how? https://gankro.github.io/blah/linear-rust/#must-be-used-at-l...
Can anyone who knows both languages enlighten us ?
More on Scala Phantom types here https://gigiigig.github.io/tlp-step-by-step/phantom-types.ht...
data State = Unbound | Bound | Listening | …
data Socket (s :: State)
(s :: State) is a phantom type, yes.Linear types can guarantee that the object participating in a state transition is not reused, which prevents accidentally referring to invalidated data.
You can use phantom types to mark the difference between bound and unbound sockets, but you need the linear type system to prevent the unbound value from being used again.
That's not the interesting part, though. It's the linear types that are giving the guarantees here, and linear type systems are rare.
This example creates a monad parametized over how many times a return value must be consumed. It has two settings: 1 (exactly once), and ω, which I’m prettying sure is the Greek abbreviation of “whatever” (zero or more times).
As far as I can tell this isn't relevant to the pattern being discussed though. All that matters is that a type can be consumed at most once, which is a capability provided by both linear and affine types. If you're worried about guaranteeing that the state machine ends gracefully (in this case, the socket being closed), then that's the role that destructors play. Indeed, the fact that destructors exist is what initially gives the impression of Rust having a linear type system; the only time that Rust doesn't guarantee exactly-once behavior (degenerating to at-most-once) is when the destructor leaks, which is pretty hard to do by accident (e.g. creating cyclical references in Rust is non-trivial).
edit: ah I see you'd extract the entire manipulation in a function which would return a terminal type or union thereof, yeah that'd work I guess.
Or it is the classical notation for the ordinal associated with the set of integers ℕ, aka any number of times.
On the other hand, as an expansion of that pattern linear type system allows ensuring (statically) the state machine reaches a terminal state. With an affine type system you could just drop the computation halfway.