You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I think we can go even bigger here. Instead of making the user realize that there is some special client required to interact with a construct at "runtime", why don't we use our compiler powers to determine what is required to capture. Each object should provide a full abstraction over the underlying resource, whether or not that needs to be done at "construction time" via, eg., CFN, or at "runtime" via, eg., the AWS SDK.
The model I'm considering here is more along the lines of mutability: objects have certain methods that will mutate itself and some that do not (post fact: I think inspired by Rust). Since wing is targeting multiple computation environments, the "initial" one is the only environment that can access the mutable methods. Other environments are only provided the immutable methods. We can do some fancy dependency analysis to determine what code needs to be captured in order to make these methods still function.
As an example, here is what a DynamoDB table would look like (the keyword to notice here is mut):
This will get compiled to the following intermediate wing code using compiler analysis to figure out which objects need to be initialized and what dead code to be removed.
register_user.inter.w
/* no change */
construct dynamodb.Client {
private sdk_client: ...
init(mut self) {
self.sdk_client = ...
}
put_item(self, item) {
self.sdk_client...
}
}
construct dynamodb.CfnTable implements aws.CfnResource {
public table_name: token
/* different than source */
init(mut self) {
self.table_name = <complied token>
}
}
/* no change, besides removing add_global_secondary_index */
class dynamodb.Table {
private cfn_table: dynamodb.CfnTable
private client: dynamodb.Client
init(mut self, partition_key: string) {
self.cfn_table = dynamodb.CfnTable(partition_key)
self.client = dynamodb.Client()
}
put_item(self, ...) {
self.client.put_item({
TableName: self.cfn_table.table_name
...
})
}
}
/* argument generated from user-supplied function */
fun main(user: object) {
/* compiled init */
users = Table()
/* end compiled init */
/* user-supplied function */
users.put_item({ ID: { S: user.id }, UserName: { S: user.name } })
/* end user-supplied function */
}
error: cannot compile closure that references a mutable object
| add_index = lambda.Function(process (id: string) -> {
| users.add_global_secondary_index(string)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `add_global_secondary_index` requires `users` to be mutable but it is immutable in this closure
Furthermore, immutable methods can be invoked in the "initial" computation environment. This goes along with the idea that initialization of some construct includes two main components: resource allocation and data injection, regardless of whether allocation means asking the OS for some memory or telling AWS to create a new resource.
(This entire idea will require some work around how we reference computation environments and which properties to capture automatically but figure that can wait until we decide this is the right direction to go.)
This discussion was converted from issue #64 on March 25, 2024 19:01.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I think we can go even bigger here. Instead of making the user realize that there is some special client required to interact with a construct at "runtime", why don't we use our compiler powers to determine what is required to capture. Each object should provide a full abstraction over the underlying resource, whether or not that needs to be done at "construction time" via, eg., CFN, or at "runtime" via, eg., the AWS SDK.
The model I'm considering here is more along the lines of mutability: objects have certain methods that will mutate itself and some that do not (post fact: I think inspired by Rust). Since wing is targeting multiple computation environments, the "initial" one is the only environment that can access the mutable methods. Other environments are only provided the immutable methods. We can do some fancy dependency analysis to determine what code needs to be captured in order to make these methods still function.
As an example, here is what a DynamoDB table would look like (the keyword to notice here is
mut
):table.w
Then, a library consumer can create a construct that listens to a queue and adds new entries to the table using an immutable method.
register_user.w
This will get compiled to the following intermediate wing code using compiler analysis to figure out which objects need to be initialized and what dead code to be removed.
register_user.inter.w
Notably, the following will fail to compile:
register_user_bad.w
compiler output
Furthermore, immutable methods can be invoked in the "initial" computation environment. This goes along with the idea that initialization of some construct includes two main components: resource allocation and data injection, regardless of whether allocation means asking the OS for some memory or telling AWS to create a new resource.
(This entire idea will require some work around how we reference computation environments and which properties to capture automatically but figure that can wait until we decide this is the right direction to go.)
Originally posted by @BenChaimberg in https://github.com/monadahq/rfcs/pull/4#discussion_r918012091
Beta Was this translation helpful? Give feedback.
All reactions