wing icon indicating copy to clipboard operation
wing copied to clipboard

Revisit `as "id"` syntax / "as" keyword is confusing

Open eladb opened this issue 2 years ago • 40 comments

We want to use as for casting, so let's revisit the way we specify an identifier for preflight objects.

One option is to replace the current usage with is:

new Bucket() is "myBucket";
new Bucket() is "yourBucket";

Another alternative is to add a synthetic keyword optional parameter called id to every preflight class initializer and plug it in:

new cloud.Bucket(); // defaults to "cloud.Bucket"

new cloud.Bucket(id: "my-bucket");

See below for some discussion.

Thoughts?

eladb avatar Oct 19 '23 18:10 eladb

I kind of prefer the old fashioned way:

new Bucket(id: "yourBucket");

But I understand it leads to verbose and even possibly confusing paths...

So, is is fine to me.

skyrpex avatar Oct 20 '23 07:10 skyrpex

Sounds good to me, I think unlocking "as" for casting would be nice.

Chriscbr avatar Oct 20 '23 16:10 Chriscbr

What's the issue # for lang support for casting? Can help putting this in context.

I like new R() as "some_R" because it conveys the idea that there's a default and we're changing it. I like new R() id "some_R" because it's explicit about what we're doing, setting a unique id for the resource. I like new R() named "some_R" because it's very clear what we're doing, creating a resource and giving it a name. is feels a bit off. So not strongly opinionated.

yoav-steinberg avatar Oct 21 '23 06:10 yoav-steinberg

Considering @skyrpex's idea. We can generate a synthetic optional keyword argument called id and then the usage will be:

new R(id: "some_R")

It's a bit of a hack, but from a DX perspective it feels right.

eladb avatar Oct 21 '23 08:10 eladb

What would this mean for struct expansion in function calls with an id field? Wouldn't this clash with the named arguments?

Lancear avatar Oct 21 '23 10:10 Lancear

Why not use to for casting instead of as?

marciocadev avatar Oct 21 '23 13:10 marciocadev

What would this mean for struct expansion in function calls with an id field? Wouldn't this clash with the named arguments?

Yes, it would clash. We can decide one of the following:

  1. To make id a reserved name for keyword arguments (could be a bit annoying)
  2. If there's an explicit id, just use this value as the logical ID. Basically "embrace the clash".
  3. Use something like $id or some other more unique name for the synthetic identifier.

At any rate, I am growing to like this direction because it does result in a very intuitive DX.

new cloud.Bucket(id: "my-bucket");
new cloud.Queue();
new cloud.Function(id: "my-function", inflight () => {
  log("hey!");
});

I really hated the fact that the identifier was something you'd have to specify outside of the call.

eladb avatar Oct 21 '23 13:10 eladb

Why not use to for casting instead of as?

as is commonly used for casting and I don't think we have a strong justification to shift the mental model here.

eladb avatar Oct 21 '23 13:10 eladb

Makes sense! Then I would go for embrace the clash. Seems like the best DX for when it clashes.

Lancear avatar Oct 22 '23 08:10 Lancear

I like new R() id "some_R" because it's explicit about what we're doing, setting a unique id for the resource.

I think the most practical way would be to replace the current as with id, as suggested by Yoav's syntax.

marciocadev avatar Oct 22 '23 18:10 marciocadev

I personally find both id and is quite odd, since I'd expect an adverb in that position.

My proposals:

new R() alias "some_R"
new R() withId "some_R"
new R() withName "some_R"

garysassano avatar Oct 22 '23 18:10 garysassano

Considering @skyrpex's idea. We can generate a synthetic optional keyword argument called id and then the usage will be:

new R(id: "some_R")

It's a bit of a hack, but from a DX perspective it feels right.

Does the id always have to be the first argument?

Chriscbr avatar Oct 23 '23 15:10 Chriscbr

Does the id always have to be the first argument?

No, it's a faux named keyword argument.

eladb avatar Oct 24 '23 19:10 eladb

When is the id named argument available? Always? I guess we don't only want to have it when the last parameter is a struct, but also allow it when the last argument is an optional or variadic?

Lancear avatar Oct 24 '23 21:10 Lancear

Good point @lancear, it should be supported for in all initializer signatures.

From an implementation perspective, the compiler will not pass it through as a keyword argument to the function but will pass it as the 2nd construct argument.

So:

new cloud.Bucket(id: "bang", encrypted: true);

Will emit the following JavaScript:

new cloud.Bucket(this, "bang", { encrypted: true });

As I said, it's a hack.

@yoav-steinberg curious what you think about this direction?

eladb avatar Oct 24 '23 21:10 eladb

new cloud.Function(id: "my-function", inflight () => {
  log("hey!");
});

The parser right now expects all positional arguments to come before named arguments. To support usage like the one above, would we have to model this basically as a special case?

Can the "id" be added anywhere in the parameter list? ie

new cloud.Service(
  inflight () => { log("started!") },
  id: "my-service",
  inflight () => { log("done!"); },
  port: 8080,
);

Chriscbr avatar Oct 24 '23 23:10 Chriscbr

The parser right now expects all positional arguments to come before named arguments.

Really? What's the motivation behind this restriction?

eladb avatar Oct 25 '23 11:10 eladb

The parser right now expects all positional arguments to come before named arguments.

I think this is great for readability, named arguments in between positional arguments can get quite difficult to read. Maybe we could ease the constraint to allow them either before or after all positional arguments, or only allow the named id argument also as the first argument.

Lancear avatar Oct 25 '23 11:10 Lancear

I can see the argument for readability but maybe that's not something the language should be strict about, and leave it to the developer to decide, no?

eladb avatar Oct 25 '23 12:10 eladb

When reading a function call, I generally expect the arguments to match the order that the function is expecting. Since struct expansion is just a sugar for struct construction as the last argument, seeing struct expansion tells me that I'm looking at the last argument.

Also worth noting we probably shouldn't call it "named arguments", as that confuses it with a similar concept in other languages that we don't support.

MarkMcCulloh avatar Oct 25 '23 13:10 MarkMcCulloh

Enforcing id can only be the first argument (if it's given) could be a reasonable compromise:

// Error: Expected "id" to be the first argument
new cloud.Function(inflight (x) => { ... }, id: "my-function",);

// Error: Expected "inflight (str): str" but got "str". Did you mean to write `id: "my-function"`? 
new cloud.Function("my-function", inflight (x) => { ... });

// OK
new cloud.Function(id: "my-function", inflight (x) => { ... });

Chriscbr avatar Oct 25 '23 16:10 Chriscbr

that would also make it clear it's not a normal struct expansion argument.

Lancear avatar Oct 25 '23 16:10 Lancear

btw is there any specific reason why we don't simply use the variable name of the resource as the id? Like that is the natural way to distinguish between instances of the same type in code already. 🤔

let myBucket = new cloud.Bucket();
new cloud.Queue(); // unnamed queue
let myFunction = new cloud.Function(inflight () => {
  log("hey!");
});

Lancear avatar Oct 26 '23 16:10 Lancear

is there any specific reason why we don't simply use the variable name of the resource as the id

Variables are too volatile to serve as identifiers. These identifiers are mapped to physical resource names, and we don't want a simple rename of an identifier to have an impact on the application's infrastructure.

eladb avatar Oct 27 '23 13:10 eladb

I am still not convinced that we need to be too strict about where keyword arguments are positioned in the function call. Requiring that id: is a special thing that must only be the first argument feels like a much higher cognitive load from a API consumer standpoint. From the consumer's perspective id: could just just be treated as another optional keyword argument.

I understand the stylistic argument (but that's not something we have to enforce), but I am not sure the fact that the struct is declared as the last argument justifies this strictness.

eladb avatar Oct 27 '23 14:10 eladb

So I personally am not strongly opinionated here but I do dislike this id argument direction, I kind of like the as "id" syntax as is.

Addressing some earlier discussions in the thread about not using as and maybe using named, alias, etc.. If this is for the sake of leaving as for casting I guess I see the point, however as would already be overloaded for not just casting but also aliasing.

bring "whatever" as lib;

So cognitively I dont think its too crazy to us new cloud.Bucket() as "my_bucket"

That said Id also be in favor of just changing new cloud.Bucket() as "my_bucket" to some thing along the lines of new cloud.Bucket() named "my_bucket"

hasanaburayyan avatar Oct 27 '23 14:10 hasanaburayyan

I've been looking for an alternative for the "as ID" syntax not only to free up the "as" keyword but also because it's a cumbersome syntax which ends up appearing a lot in the language. The id: idea turns this into something much less messy in my opinion, which is why I am excited to explore it.

This is ugly from an aesthetic/stylistic point of view:

new cloud.Function(inflight () => {
  log("function 2");
}) as "f1";

new cloud.Function(inflight () => {
  log("function 2");
}) as "f2";

This is much better:

new cloud.Function(id: "f1", inflight () => {
  log("function 2");
});

new cloud.Function(id: "f2", inflight () => {
  log("function 2");
});

I also think it will reduce the cognitive load because it's one less concept of understand when you are creating objects.

eladb avatar Oct 27 '23 15:10 eladb

This is ugly from an aesthetic/stylistic point of view:

Yea but maybe its like a pug or chihuahua, soo ugly its cute 😁

hasanaburayyan avatar Oct 27 '23 17:10 hasanaburayyan

Maybe.

eladb avatar Oct 27 '23 19:10 eladb

A bit torn between as (or similar named keywords) and something like a virtual argument. While I found the as "myid" syntax odd in the beginning, it's nicely conveying that it's not a functional aspect of the construct, but something which serves a special purpose. Also, it's easy to add / remove on existing constructs.

On the other hand, having actual arguments probably fits better in the mental model of how to create and name constructs (in particular people coming from a cdk background).

Speaking of cdk, how would this work for cdk dependencies (e.g. resources from the @cdktf/aws-provider).

skorfmann avatar Oct 28 '23 14:10 skorfmann