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

Use JS Symbol for sum ADTs #4540

Conversation

shybovycha
Copy link

@shybovycha shybovycha commented Mar 19, 2024

Description of the change

Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR.

Currently PureScript generates a quite outdated, lengthy javascript code for sum ADTs:

data Msg = Increment | Decrement

produces the following JS in 0.15.15:

// Generated by purs version 0.15.15
var Increment = /* #__PURE__ */ (function () {
    function Increment() {

    };
    Increment.value = new Increment();
    return Increment;
})();
var Decrement = /* #__PURE__ */ (function () {
    function Decrement() {

    };
    Decrement.value = new Decrement();
    return Decrement;
})();

When comparing the above ADTs (with pattern matching or if expressions), the code generated is as follows:

main :: Msg -> String
main Increment = "42"
main Decrement = "0"

produces in 0.15.15:

var main = function (v) {
    if (v instanceof Increment) {
        return "42";
    };
    if (v instanceof Decrement) {
        return "0";
    };
    throw new Error("Failed pattern match at Main (line 9, column 1 - line 9, column 22): " + [ v.constructor.name ]);
};

A better approach would be to use Symbol class, available in all the modern browsers and Node.JS versions:

// Generated by purs version 0.15.16
var Increment = /* #__PURE__ */ Symbol("Increment");
var Decrement = /* #__PURE__ */ Symbol("Decrement");

so that then the pattern matching can make use of equality (which is stricter than instanceof):

var main = function (v) {
    if (v === Increment) {
        return "42";
    };
    if (v === Decrement) {
        return "0";
    };
    throw new Error("Failed pattern match at Main (line 9, column 1 - line 9, column 22): " + [ v.constructor.name ]);
};

This PR introduces two changes:

  1. generating Symbol for sum ADTs
  2. comparing the corresponding symbols for pattern matching sum ADTs using equality operator rather than instanceof

This should make the generated JS code more reliable when comparing values and shorter (resulting in smaller bundles).

For parametrized types this would still work as before, generating the old-style code:

data Tree a = Leaf a | Empty

main :: forall a. Tree a -> String
main (Leaf _) = "leaf"
main Empty = "empty"

and the JS output:

var Leaf = /* #__PURE__ */ (function () {
    function Leaf(value0) {
        this.value0 = value0;
    };
    Leaf.create = function (value0) {
        return new Leaf(value0);
    };
    return Leaf;
})();
var Empty = /* #__PURE__ */ (function () {
    function Empty() {

    };
    Empty.value = new Empty();
    return Empty;
})();
var main = function (v) {
    if (v instanceof Leaf) {
        return "leaf";
    };
    if (v instanceof Empty) {
        return "empty";
    };
    throw new Error("Failed pattern match at Main (line 9, column 1 - line 9, column 35): " + [ v.constructor.name ]);
};

Checklist:

  • Added a file to CHANGELOG.d for this PR (see CHANGELOG.d/README.md)
  • Added myself to CONTRIBUTORS.md (if this is my first contribution)
  • Linked any existing issues or proposals that this pull request should close
  • Updated or added relevant documentation
  • Added a test for the contribution (if applicable)

@shybovycha shybovycha marked this pull request as ready for review March 19, 2024 13:24
@natefaubion
Copy link
Contributor

As I understand it, this PR changes enumeration-like constructors (no arguments) to Symbols. See discussion in #4020 for the more general issue of data type encoding. The optimized representation is currently implemented in https://github.com/aristanetworks/purescript-backend-optimizer with the additional options of supporting --int-tag and unwrapped constant values for enumerations. The advantage of non-symbols being that you can potentially transfer them between JS realms or construct them without having access to the original Symbol.

@JordanMartinez
Copy link
Contributor

Should this PR be closed since we've not yet reached a conclusion (AFAIK) in #4020? Also, since backend-optimizer already does such an optimization, is this still needed?

@shybovycha
Copy link
Author

Thanks for the pointers! Seems like it is a quite long-running debate.

My comment was getting a tad too long and generic (relative to this PR), so I will rather add it on #4020 .

TL;DR:

  • currently the generated code is not really good for sharing with the outside JS either
  • having int or string tag in an object has the exact same issues as the current approach
  • why not combine tag with Symbol and toggle it with a compiler flag (when on - generate more safe code, when off - generate smaller code)?

@shybovycha shybovycha closed this Mar 20, 2024
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

3 participants