winglang / wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
https://winglang.io
Other
4.59k stars 180 forks source link

Compiler intrinsics (or, "special macros") #6378

Closed MarkMcCulloh closed 1 week ago

MarkMcCulloh commented 2 weeks ago

Use Case

In wing there are currently several built-in globals, all of which are injected with their own macro functionalities. These can intentionally have behaviors different from those possible to implement in wing itself. Some are simply macros that emit code, while some interact with the compiler in more novel/deep ways (e.g. lift). Even the simple macros have access to information that user-defined code would typically not (types, source position, etc.).

These are incredibly useful, but their behavior is surprising when to the user they appear no different than any other symbol/variable. Additionally they allow you to do things like do variable shadowing and assign them to variables which can actually break their functionality.

Proposed Solution

Introduce a new syntax to reflect these special behaviors.

I propose @x() as a "compiler intrinsic", with the following notes:

With this, we can convert our existing globals to intrinsics instead and go forward implementing new ones. Some existing issues that I think would benefit:

Other thoughts

I think it would be reasonable to expose the ability to create these in user-space via some sort of compiler API. This would be amazing but the design and implementation that would be a pretty huge task. Even if we did, we could still use the same syntax for invoke them even though we probably shouldn't call them "intrinsic" anymore.

Community Notes

Chriscbr commented 2 weeks ago

This design feels well thought out. I think it addresses a significant gap / confusion in the language today (how come built-ins functions like log, assert, and lift function work different from user defined functions) and leads to more consistency than the alternative of designing dedicated syntax for each builtin function.

While the syntax resembles the decorator syntax used in some other languages, I think the confusion is reduced since the intrinsics appear in an expression position.

I don't think there are many capabilities that would be more useful to reserve "@" for. One idea that crossed my mind was using @ to designate explicit lifting, e.g.


let b = new cloud.Bucket();

inflight () => {
  @b.put("key", "value");  
}

But even this idea doesn't feel that strong since an expression like @foo.bar.baz doesn't really make it clear "how much" of the expression is from preflight vs inflight. So using @ for intrinsic functions seems more appropriate.

I'm also in agreement that user-defined intrinsics / macros don't need to be designed at this stage.

@MarkMcCulloh Do you want to add this to a RFC and post it in the #dev channel?

eladb commented 2 weeks ago

In favor of this change!

While the arguments/return types must be normal wing types

I am not 100% sure that we need to take this constraint. For example, the @log() intrinsic needs to accept any, which is not a type users can declare, right?

Let's make sure we list all the current builtins and get a feel of what it looks like to use them. For example, @log is doing to take time to get used to:

new cloud.Function(inflight () => {
  @log("hello, world");
});

Another suggestion - especially if we change log to @log is to do a soft deprecation with a compiler warning and only hard-deprecate it later. We also discussed that warnings will have a quickfix in VSCode (I know we don't have those but it's about time).

We can't afford to hard deprecate log() anymore.

staycoolcall911 commented 2 weeks ago

I'll just say that I'm not sure about adding @ to builtins. It doesn't add any information or readability, and it adds an extra (irregular) char. log() is pretty intuitive for programmers - it makes sense that it is a builtin. If we need the intrinsic character for @inflight maybe we can just use it for that?

eladb commented 2 weeks ago

There is value in having an explicit way to denote built-ins in order to signal to developers that this thing might not behave like a normal function and that it might not be something you can actually define on your own (at least for now).

Specifically for log(), I think we can be pragmatic and support both @log and log because it's such a common builtin to use, so we can be pragmatic about it for the sake of ergonomics. Maybe we can even make this exception for assert.

But for stuff like @lift and @inflight and @extern I think it makes a lot of sense.

The other benefit is that if we make something like extern a keyword, users can't use it as a variable name.

yoav-steinberg commented 1 week ago

I'm for this change, even trying out @log while deprecating log (it's still less chars than print or console.log).

MarkMcCulloh commented 1 week ago

Do you want to add this to a RFC and post it in the #dev channel?

I'm not against an RFC but I'm not sure what open questions need to be answered. As an idea the @ is pretty simple since it represents something we already have in our compiler. Individual intrinsics could support some discussion I suppose.

While the arguments/return types must be normal wing types

I am not 100% sure that we need to take this constraint. For example, the @log() intrinsic needs to accept any, which is not a type users can declare, right?

I think "normal wing types" can be pretty flexible, so I maybe should phrase it differently. I guess I moreso mean "representable in wing's type system". So "any" would be valid. I figured it was useful to add so that we can still make use of wing's type system, but maybe it's not really useful as a constraint to always have.

monadabot commented 1 week ago

Congrats! :rocket: This was released in Wing 0.73.38.