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

JavaScript code generation for methods #30

Merged
merged 3 commits into from Dec 2, 2021
Merged

JavaScript code generation for methods #30

merged 3 commits into from Dec 2, 2021

Conversation

chengluyu
Copy link
Member

@chengluyu chengluyu commented Nov 30, 2021

Todos

  • Make it work on basic examples.
  • Translate nullary methods to get property. Because we didn't implement calling nullary methods so far.
  • Inheritance.
  • Method override.

Examples

Input

class Box[T]: { inner: T }
  method Map f = Box { inner = f this.inner }

def Box value = Box { inner = value }

def box1 = Box 1
def box2 = box1.Map (fun x -> add x 1)

Output

val Box: 'a -> box & {inner: 'a} = (value) => new Box({ inner: value })
val box1: box & {inner: 1} = Box { "inner": 1 }
val box2: box & {inner: int} = Box { "inner": 2 }

Generated

class Box {
  constructor(fields) {
    this.inner = fields.inner;
  }
  Map(f) {
    return new Box({ inner: f(this.inner) });
  }
}
const Box0 = (value) => new Box({ inner: value });
const box1 = Box0(1);
const box2 = box1.Map((x) => x + 1);

@LPTK
Copy link
Contributor

LPTK commented Nov 30, 2021

Cool! Now try it with inheritance and overriding :^D

Tip: you can just ignore method signatures in the source (those declarations of the form Mtd: <some type>).

@LPTK
Copy link
Contributor

LPTK commented Nov 30, 2021

Technical note: for this one, let's wait until #22 is merged and squashed (Cc @fo5for). Then we can merge directly into the main mlscript branch. But the PR is fine as it is right now.

Base automatically changed from methods to mlscript December 1, 2021 02:12
@chengluyu
Copy link
Member Author

chengluyu commented Dec 1, 2021

Test Cases

Translate Nullary Methods into Getters

Input

class Box[T]: { inner: T }
  method Map f = Box { inner = f this.inner }
  method Get = this.inner

def Box value = Box { inner = value }

def boxOne = Box 1
def one = boxOne.Get
def boxTwo = boxOne.Map (fun x -> add x 1)
def two = boxTwo.Get

Output

val Box: 'a -> box & {inner: 'a} = (value) => new Box({ inner: value })
val boxOne: box & {inner: 1} = Box { "inner": 1 }
val one: 1 = 1
val boxTwo: box & {inner: int} = Box { "inner": 2 }
val two: int = 2

Inheritance

Input

class Box[T]: { inner: T }
  method Map f = Box { inner = f this.inner }
  method Get = this.inner

class DecoratedBox[T]: Box[T] & { motif: string }
  method GetBox: Box[T]
  method GetBox = this
  method GetMotif = this.motif
  method Decorate motif = DecoratedBox {
    inner = this.inner;
    motif
  }

def flowerBox = DecoratedBox { inner = 42; motif = "flower" }
def grassBox = flowerBox.Decorate "grass"

def FLOWER = flowerBox.GetMotif
def GRASS = grassBox.GetMotif

Output

val flowerBox: decoratedBox & {inner: 42, motif: "flower"} = DecoratedBox { "inner": 42, "motif": "flower" }
val grassBox: decoratedBox & {inner: 42, motif: "grass"} = DecoratedBox { "inner": 42, "motif": "grass" }
val FLOWER: string = "flower"
val GRASS: string = "grass"

Method Override

Input (from test cases)

class AbstractPair[A, B]: { x: A; y: B }
    method Test: (A -> B -> bool) -> bool
    method Map[C, D]: (A -> C) -> (B -> D) -> AbstractPair[C, D]

class Pair[A, B]: AbstractPair[A, B]
    method Test(f: A -> B -> bool) = f this.x this.y
    method Map fx fy = Pair { x = fx this.x; y = fy this.y }

class True[A, B]: Pair[A, B]
    method Test f = true
    method True = this.Test (fun x -> error)

Generated code

class AbstractPair {
  constructor(fields) {
    this.x = fields.x;
    this.y = fields.y;
  }
}
class Pair extends AbstractPair {
  constructor(fields) {
    super(fields);
  }
  Test(f) {
    return f(this.x)(this.y);
  }
  Map(fx) {
    return ((fy) => new Pair({
      x: fx(this.x),
      y: fy(this.y),
    }));
  }
}
class True extends Pair {
  constructor(fields) {
    super(fields);
  }
  Test(f) {
    return true;
  }
  get True() {
    return this.Test((x) => error);
  }
}

@chengluyu
Copy link
Member Author

Hmm, I should implement error in generated code. Maybe in another PR?

@chengluyu chengluyu marked this pull request as ready for review December 1, 2021 04:12
@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

Great, looking good!

I should implement error in generated code. Maybe in another PR?

Yeah, it should probably be implemented as throwing an exception. Let's do it as part of this PR.

@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

Also, let's merge this first, and soon we'll add proper JS code-gen testing and revisit the tests you described above.

@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

@chengluyu This branch needs to be rebased on mlscript and remove all commits before 7ad32d4 (these commits were squashed in the previous PR). Let me know if you need help doing that. You can look into git rebase -i (interactive rebase).

@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

@chengluyu Actually since there are many conflicts, it would be easier to hard-reset this branch to the current HEAD of mlscript and then git-cherrypick your commits back one by one. Then force-push the new history. Or this can also be done as part of the git rebase -i command (drop all commits previously squashed).

@chengluyu
Copy link
Member Author

[...] it would be easier to hard-reset this branch to the current HEAD of mlscript and then git-cherrypick your commits back one by one. Then force-push the new history. [...]

I proposed a lot of changes to JSBackend.scala in PR #29. How about do this after when that PR was merged?

@chengluyu
Copy link
Member Author

BTW, I have an idea of merging multiple function defs into one. For example, we can translate

def f { x = 0 } = 1
def f { x = 1 } = 0

into

const f = (x) => {
  if (x === 0) { return 1; }
  else if (x === 1) { return 0; }
  else { /* unreachable */ }
}

@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

I have an idea of merging multiple function defs into one.

But this is not in line with MLscript semantics, which uses shadowing. In MLscript, f as defined above will always return value 0.

@LPTK
Copy link
Contributor

LPTK commented Dec 1, 2021

To be more specific, what would you do in this case, for instance?

def f 0 = "oops"
def f x = x

f 0 + 1 // valid in MLscript

@LPTK LPTK merged commit 4be5e2f into mlscript Dec 2, 2021
@LPTK LPTK deleted the methods-js branch December 2, 2021 05:19
This was referenced Dec 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants