Replies: 5 comments
-
Great read! And thanks for the credit, but my suggestion was a bit different and less good, to have to explicitly assign lifted expressions into immutable variables in inflight. I think @eladb then suggested the much better |
Beta Was this translation helpful? Give feedback.
-
Love it. Follow up: What's the syntax/pattern/idiom for declaring classes and their lifted (aka inflight) api? |
Beta Was this translation helpful? Give feedback.
-
I wasn't in the team discussion so I am probably missing some context, which is actually not a good thing because non of Wing users will have that context. I am not sure I understand the user value for the But the idea itself is intriguing, have you consider this lift mechanism as method on an preflight class (maybe even using the existing If we consider fields and variables the same way (@eladb has been talking about adapting the kotlin constructor) it feels that a well defined default implementation of I also think that this gives a nice connection to other language, for example in typescript class CounterClient {
}
class Counter {
lift() { // or inflight init
return new CounterClient(...)
}
} |
Beta Was this translation helpful? Give feedback.
-
The lifting mechanism brings with it some non-standard behaviors, for instance that the lifted data is immutable in inflight. let var x = 1;
inflight () => {
x = x + 1;
}; you expect it to work, but if you have to write something like this: let var x = 1;
inflight () => {
lift x = lift x + 1;
};
It is more clear why it is not valid |
Beta Was this translation helpful? Give feedback.
-
I think this is closely related to to #4564. That issue exposes the unexpected behavior of the inflight ctor being executed per client (or per lift). So any changes you perform on inflight (client) data isn't valid across different lifts. Also any global state (a bucket for example) modifier in the inflight ctor is also modifier again when there's another lift. Dumping some syntax thoughts:
|
Beta Was this translation helpful? Give feedback.
-
This write-up is inspired by an offline discussion with the compiler team about how to handle the issue of
MutArray<T>
functionally behaving likeArray<T>
once it is lifted into inflight, where @ShaiBer had the idea of "what if lift was a keyword?". The idea here is just to explore the concept and see whether that would give us any new expressive capabilities to the language.To start, suppose there is an operator in the language called "lift". It's an operator you use before an expression, similar to "await" in JavaScript or "go" in Golang. An example expression that uses it is
lift x
. The rules of the operator is that givenx
has typeT
, thenlift x
has typeLifted<T>
. Ok, so what does it do?Well,
lift
takes a value that was defined in preflight, and transforms it into a form you can use inflight. The "Lifted" modifier changes what operations are allowed on the type. For example,Bucket
has the methodsaddObject
andonCreate
, performinglift
on a bucket gives you aLifted<Bucket>
which has the methodsput
andget
. Basically,Lifted<T>
would give us a way to model in our type system how it is that inflight code is magically changing what operations we can call on objects.lift
would be an irreversible operation (you can't un-lift something), and lifting a value that has already been lifted is a no-op (similar toawait
in JS). (Lifting an already-lifted value may or may not be allowed, I'm not sure this detail is consequential.)How does this work with Wing code today? Well, in most code today you haven't had to explicitly use
lift
because the Wing compiler will automatically adds it for you most of the time. For example, in this snippet:The compiler would really be providing you with syntax sugar for this:
Notice how "counter" and "bucket" both had to be lifted in the inflight code.
This isn't too unlike how the Rust compiler automatically adds "*" in front of values to save you some keystrokes when you are accessing fields or methods of a value behind a pointer.
What's useful about
Lifted<T>
is it lets us articulate what operations are possible on certain types. For example, we can decide thatLifted<MutArray<T>>
has the same semantics asArray<T>
, since you can only perform readonly operations on the array once it has been lifted. This would resolve the question of "how is the type of my MutArray value changing between preflight and inflight?". When you mouse over a liftedMutArray<num>
, you would see the type annotated asLifted<MutArray<num>>
or perhapslifted MutArray<num>
.If
T
is a preflight function type (like the type ofbucket.onCreate
), thenLifted<T>
could simply be the bottom type ("never") - a clean way to model the fact that preflight functions cannot be used inflight.It also might give us a convenient solution to some problems we have with structs and serialization. Today, there's a limitation where a Wing struct cannot contain fields of certain types like preflight function types because if you did, it wouldn't be possible to lift the struct into inflight. However, with the
Lifted<T>
syntax, it's easy to define a semantics that once a struct has been lifted, you can only access the fields asLifted<T>
versions of them.Another interesting thing about
lift
is that because it's an operator, in theory you could use it anywhere. To keep things simple, we might start by only allowing you to uselift
when you're inflight. But, suppose you did calllift
on something in a preflight scope. What kinds of use cases would that enable?For one, it would give you the control to create multiple lifted copies (or "snapshots") of values. For example:
The "lift" operator also closes the loop of explaining why you can't reassign to a variable that has been lifted inflight.
lift x
is not the same asx
, hence:desugars into:
and
lift x
isn't valid on the left-hand side of an equal sign. This is similar to how you can't writein TypeScript.
Beta Was this translation helpful? Give feedback.
All reactions