wing icon indicating copy to clipboard operation
wing copied to clipboard

rfc: `unphased` functions

Open Chriscbr opened this issue 2 years ago • 17 comments

This doc proposes how phase-independent functions should look and behave in Winglang. Related to #435

From a compiler perspective, these functions would essentially be code generated in both the preflight JS class and the inflight JS class.

By submitting this pull request, I confirm that my contribution is made under the terms of the Monada Contribution License.

Chriscbr avatar Mar 03 '23 03:03 Chriscbr

Another idea/suggestion is to designate these functions with the keyword pure

Chriscbr avatar Mar 03 '23 19:03 Chriscbr

Im on board, I just dont much care for the prefixed ? maybe a keyword instead like pure but no strong opinions here.

hasanaburayyan avatar Mar 03 '23 19:03 hasanaburayyan

I agree that letting the compiler infer whether a function is phase-independent is not the right way to go. I also agree that making functions phase-independent should not be the default because it sends the wrong message about which type of function we expect to be more prevalent if writing correct wing code.

Like @hasanaburayyan, I'm also not crazy about the ?inflight keyword. I like Rust's approach with maybe-inflight better, but it also doesn't feel like the best solution to me. I hope someone will come up with a better alternative everyone will like.

I'm also OK with experimenting with having to always put a phase modifier on functions, and then we have preflight, inflight and maybe-inflight (or ?inflight or something better someone else will come up with)

ShaiBer avatar Mar 05 '23 12:03 ShaiBer

@Chriscbr , great doc Can you also add the reasoning and need for phase-independent methods, have you came across any concrete use cases?

ekeren avatar Mar 05 '23 13:03 ekeren

Perhaps an alternative is to create functions that are neither inflight nor preflight, some flight functions and add the prefix when assigning to a variable

This type of function only becomes executable when the prefix is assigned

something like

// a template function, if no variable receives this template function
// with a prefix (pre/in) the compiler gives an error 
let f = flight(...) { ... }; 
let p = pre f; // create a preflight function
let i = in f; // create a inflight function

marciocadev avatar Mar 05 '23 14:03 marciocadev

@Chriscbr , great doc Can you also add the reasoning and need for phase-independent methods, have you came across any concrete use cases?

Maybe I'm missing something, but I think any project has general purpose utility functions that are used throughout the code and have no business being confined to only one phase. For example: sorting an array with different algorithms

ShaiBer avatar Mar 05 '23 16:03 ShaiBer

Yes, I think @ShaiBer 's answer is correct, the main use case that comes to my mind is utility functions that do not need to be restricted to one phase or another. If I think of any more use cases I'll update the proposal.

BTW, my initial guess is we don't need this feature for beta, or at least we should dogfood more before considering it.

@marciocadev that's a very interesting idea! From an implementation angle, I think that could be a good option if we want to simplify modeling phase-independent functions in the compiler (perhaps we would only need our Phase enum to have two choices instead of three).

But I'm a little cautious about templating based solutions / not sure I understand the benefits. In your example, suppose I use a preflight function inside f (this means it can only be used in preflight). Would I only get a compiler error once I write let i = in f? Or would I get an error on the let f = line?

Chriscbr avatar Mar 06 '23 01:03 Chriscbr

I was thinking in gives an error if f is declare and never used to create a inflight or preflight function

Some errors

bring cloud;

let f = flight ( ... ) { ... } // ERROR, something like f variable never used
bring cloud;

let f = flight ( ... ) { ... }

new cloud.Function(pre f); // ERROR, can not pass a preflight to cloud.Function

This works

bring cloud;

let f = flight ( ... ) { ... }

new cloud.Function(in f); // WORKS

Somehow, I think of a preflight function as a function that will only be performed during the stack deploy. It is something that can be executed during the deploy phase, either before building the stack, during its construction, or after it has been built.

bring cloud;

let f = flight ( ... ) { ... } // verify some data
let p = pre f;
if (p.invoke() == false) { // verify before create the cloud.Function (preflight)
   ...
}

let i = in f;

new cloud.Function(inflight(event: str): str => {
   ...
  if (i.invoke() == true) { // verify inside cloud.Function (inflight)
     ...
  }
  ...
}

if (p.invoke() == true) { // verify after create the cloud.Function (preflight)
   ...
}

or the same without create new variables

bring cloud;

let f = flight ( ... ) { ... } // verify some data
if ((pre f).invoke() == false) { // verify before create the cloud.Function (preflight)
   ...
}

new cloud.Function(inflight(event: str): str => {
   ...
  if ((in f).invoke() == true) { // verify inside cloud.Function (inflight)
     ...
  }
  ...
}

if ((pre f).invoke() == true) { // verify after create the cloud.Function (preflight)
   ...
}

marciocadev avatar Mar 06 '23 11:03 marciocadev

Another naming suggestion from @MarkMcCulloh is "unphased"

Chriscbr avatar Mar 28 '23 18:03 Chriscbr

Hi,

This PR has not seen activity in 20 days. Therefore, we are marking the PR as stale for now. It will be closed after 7 days. If you need help with the PR, do not hesitate to reach out in the winglang community slack at winglang.slack.com. Feel free to re-open this PR when it is still relevant and ready to be worked on again. Thanks!

github-actions[bot] avatar Apr 18 '23 06:04 github-actions[bot]

Hi,

This PR has not seen activity in 20 days. Therefore, we are marking the PR as stale for now. It will be closed after 7 days. If you need help with the PR, do not hesitate to reach out in the winglang community slack at winglang.slack.com. Feel free to re-open this PR when it is still relevant and ready to be worked on again. Thanks!

github-actions[bot] avatar May 11 '23 06:05 github-actions[bot]

Hi,

This PR has not seen activity in 20 days. Therefore, we are marking the PR as stale for now. It will be closed after 7 days. If you need help with the PR, do not hesitate to reach out in the winglang community slack at winglang.slack.com. Feel free to re-open this PR when it is still relevant and ready to be worked on again. Thanks!

github-actions[bot] avatar Jun 17 '23 06:06 github-actions[bot]

I'd expect Wing to infer the phase of the function most of the time, and also cast a function to inflight if necessary. In the same way that TypeScript can infer that the following function returns a number:

export const sum = (a: number, b: number) => {
  return a + b;
};

Regarding Chris' point:

When using a Wing library, consumers should be able to rely on the fact that the method always works in that phase in the future, even if it the implementation changes

I like Mark's unphased keyword to enforce that a function works in both preflight and inflight phases.

Here's how I'd like it to work in different scenarios:

Compiler infers that the function is inflight

bring cloud;

new cloud.Function(() => {
  return "hello";
});

Compiler can cast phase-independent functions to inflight (if they don't interact with resources nor capture mutable state)

bring cloud;

let sum = (a: num, b: num) => {
  return a + b;
}

log(sum(1, 1));

new cloud.Function(() => {
  return sum(2, 2);
});

Compiler infers the function is inflight so it has access to bucket's inflight API

bring cloud;

let bucket = new cloud.Bucket();

new cloud.Function(() => {
  bucket.put("hello", "world");
});

Compiler infers that "put" is preflight because it accesses the bucket

bring cloud;

let bucket = new cloud.Bucket();

let putFile = (key: str, value: str) => {
  bucket.put(key, value);
  // ^ Error: "bucket.put" is an inflight method but "putFile" is a preflight method. Try adding the inflight keyword to "putFile".
};

Compiler infers that "append" is preflight because it captures mutable state

bring cloud;

let mut state = "";

let append = (value: str) => {
  state = "${state} ${value}";
};

new cloud.Function(() => {
  append("hello");
  // ^ Error: Can't use "append" because it's a preflight function that captures mutable state.
});

skyrpex avatar Aug 29 '23 15:08 skyrpex

@skyrpex Feel free to add a +1 to the issue about inferring function phases here: https://github.com/winglang/wing/issues/3755

The intuition in your examples is correct - I think unphased functions should be possible to use both in places that expect preflight functions and places that expect inflight functions.

Chriscbr avatar Aug 29 '23 21:08 Chriscbr

Hi,

This PR has not seen activity in 20 days. Therefore, we are marking the PR as stale for now. It will be closed after 7 days. If you need help with the PR, do not hesitate to reach out in the winglang community slack at winglang.slack.com. Feel free to re-open this PR when it is still relevant and ready to be worked on again. Thanks!

github-actions[bot] avatar May 05 '24 06:05 github-actions[bot]

I've added an addendum with a proposed mechanism for how unphased extern functions can be implemented. It's not a high priority to support this immediately, but this will help us build a path forward for rewriting various parts of the util and fs modules in Wing.

Chriscbr avatar Jul 12 '24 21:07 Chriscbr

Random thought: unphased vs universal?

skyrpex avatar Jul 26 '24 10:07 skyrpex