Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics support #104

Open
Tracked by #194
Chriscbr opened this issue Sep 20, 2022 · 7 comments
Open
Tracked by #194

Generics support #104

Chriscbr opened this issue Sep 20, 2022 · 7 comments
Labels
📜 lang-spec-impl Appears in the language spec roadmap 📐 language-design Language architecture roadmap

Comments

@Chriscbr
Copy link
Contributor

Chriscbr commented Sep 20, 2022

Supporting Generics in Wing could be used to provide useful errors between preflight and inflight code:

bring cloud;

struct A {
  field1: str;
  field2: num;
}

let bucket = new cloud.Bucket();
let queue = new cloud.Queue<A>();

let processor = new cloud.Function((event: Array<A>) ~> {
    print("Received a batch where the first message is ${event[0]}");
});
queue.onMessage(processor);
bucket.onUpload(processor); // ERROR: Bucket.onUpload expects an argument of type Function<BucketUploadEvent> but received Function<Array<A>>

let notifier: cloud.Function<cloud.BucketUploadEvent> = new cloud.Function((event: cloud.BucketUploadEvent) ~> {
    print("File ${event.file} was uploaded on {event.date}");
});
bucket.onUpload(notifier);
queue.onMessage(notifier); // ERROR: Queue.onMessage expects an argument of type Function<Array<A>> but received Function<BucketUploadEvent>

To support JSII interoperability, I'd propose that during compilation we might be able to perform type erasure like Java that convert type paramters like T into any, and add JSII metadata that details any of the erased type information.

Chriscbr pushed a commit that referenced this issue Sep 22, 2022
Upgrades project dependencies. See details in [workflow run].

[Workflow Run]: https://github.com/monadahq/wingsdk/actions/runs/3075234159

------

*Automatically created by projen via the "upgrade-main" workflow*
@Chriscbr Chriscbr added the 📐 language-design Language architecture label Sep 30, 2022
@github-actions
Copy link

github-actions bot commented Dec 9, 2022

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days.
Feel free to re-open this issue when there's an update or relevant information to be added.
Thanks!

@github-actions github-actions bot added the Stale label Dec 9, 2022
@Chriscbr
Copy link
Contributor Author

Chriscbr commented Dec 9, 2022

Keep

@github-actions
Copy link

github-actions bot commented Feb 9, 2023

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days.
Feel free to re-open this issue when there's an update or relevant information to be added.
Thanks!

@skyrpex
Copy link
Contributor

skyrpex commented Aug 29, 2023

Some use cases:

Input Validation

Cloud functions need to validate input if they can be invoke from the outside. After validating the input, the function can safely assume that the input is correct and can be used without further checks.

Let's use Zod as an example:

bring cloud;
bring zod;

let InputSchema = zod.object({
    name: zod.string(),
    age: zod.number(),
});

new cloud.Function(inflight (input: unknown) => {
    let user = InputSchema.parse(input);
    log(user.username);
    log(user.age);
});

A different syntax example:

bring cloud;
bring zod;

new cloud.Function({
    input: zod.object({
        name: zod.string(),
        age: zod.number(),
    }),
    handler: inflight (input) => {
        //              ^? input is inferred to be the type of the input schema
        log(input.name);
        log(input.age);
    },
});

Type-safe Database Queries

If we can defined what shape of data belongs to a queue or a table, user code will be type safe:

bring cloud;

interface User {
    id: string;
    name: string;
    age: number;
}

let users = new cloud.Table<User>(
    name: "users",
    primaryKey: "id", // Should be prompted by the IDE with "id", "name" and "age"
);

new cloud.Function(inflight () => {
    // Can't really add invalid data thanks to the type system.
    users.put({
        id: "1",
        name: "John",
        age: 20,
    });
});

Enable type-safe Single-Table Design

Learn about Single-Table Design here: https://www.alexdebrie.com/posts/dynamodb-single-table/.

A feature like this would require Template Literal Types,

bring cloud;

interface UserItem {
    pk: `user#${string}`;
    userId: string;
    name: string;
    age: number;
}

interface ProjectItem {
    pk: `project#${string}`;
    projectId: string;
    name: string;
    createdAt: number;
}

let table = new cloud.Table<UserItem | ProjectItem>(
    name: "table",
    primaryKey: "pk",
);

new cloud.Function(inflight () => {
    let userId = "user_1";
    table.put({
        pk: "user#${userId}",
        userId: userId,
        name: "John",
        age: 20,
    });

    let projectId = "project_1";
    table.put({
        pk: "project#${projectId}",
        projectId: projectId,
        name: "Project 1",
        createdAt: Date.now(),
    });
});

Avoid awkward manual JSON handling

With generics enabling to define the shape of the data in constructs, there's no need to work with the Json class most of the time.... We could work with typed objects directly.

@Chriscbr
Copy link
Contributor Author

@skyrpex It might be possible to handle several schema validation use cases through fromSchema:

bring cloud;

struct Person {
  name: str;
  age: num;
}

let fn = new cloud.Function(inflight (input: Json) => {
  let person = Person.fromJson(input);
  log(person.name);
  log(person.age);
  return person;
);

@skyrpex
Copy link
Contributor

skyrpex commented Sep 4, 2023

@skyrpex It might be possible to handle several schema validation use cases through fromSchema:

bring cloud;

struct Person {
  name: str;
  age: num;
}

let fn = new cloud.Function(inflight (input: Json) => {
  let person = Person.fromJson(input);
  log(person.name);
  log(person.age);
  return person;
);

Yes but this has some problems:

  • You will use fn around your codebase without having type inferring, so you never know what data you have to pass for it to work
  • If fn is not exposed to the outside, data validation doesn't need to happen at runtime. The compiler can take care of it. Every saved tick is welcome

@Chriscbr
Copy link
Contributor Author

I've been thinking more about the idea of changing the type of the handler you pass to cloud.Function, from inflight (str): str? to inflight (Json): Json?. Before I thought it would solve some problems, but I'm actually starting to feel like it's not really much of an improvement.

Compare these two cloud functions that accept and return a number/id -- they both require a similar amount of input/output parsing:

let fn1 = new cloud.Function(inflight (input: str): str => {
  let var val = num.fromStr(input);
  val += 1;
  return "{val}";
});

let fn2 = new cloud.Function(inflight (input: Json): Json? => {
  let var val = num.fromJson(input);
  val += 1;
  return Json val;
});

And of course, you also have to convert between num and str/Json each time you call invoke() on the function, so no effort is saved there.

For now, I'd recommend using wrappers like this:

bring cloud;

class NumFunction {
  fn: cloud.Function;
  new(handler: inflight (num): num) {
    let strHandler = inflight (input: str): str => {
      let numInput = num.fromStr(input);
      let numOutput = handler(numInput);
      return "{numOutput}";
    };
    this.fn = new cloud.Function(strHandler);
  }

  pub inflight invoke(input: num): num {
    let inputStr = "{input}";
    let outputStr = this.fn.invoke(inputStr);
    return num.fromStr(outputStr);
  }
}

let fn = new NumFunction(inflight (input: num): num => {
  return input + 1;
});

test "fn(1) == 2" {
  assert(fn.invoke(1) == 2);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📜 lang-spec-impl Appears in the language spec roadmap 📐 language-design Language architecture roadmap
Projects
Status: 🤝 Backlog - handoff to owners
Status: Todo - p2
Development

No branches or pull requests

3 participants