sanctuary-js / sanctuary

:see_no_evil: Refuge from unsafe JavaScript
https://sanctuary.js.org
MIT License
3.03k stars 94 forks source link

Adopt subset of ES6 #558

Closed paldepind closed 4 years ago

paldepind commented 6 years ago

This was discussed in another issue (I've forgotten which one) but I think it deserves its own issue and I don't think one already exists.

Would it make sense for Sanctuary to adopt some subset of ES6 features? The subset could consist of:

As an example of the benefits of => consider the code below from Sanctuary:

function on(f) {
  return function(g) {
    return function(x) {
      return function(y) {
        return f (g (x)) (g (y));
      };
    };
  };
}

With arrow functions it would be:

function on(f) {
  return (g) => (x) => (y) => f (g (x)) (g (y));
}

I think the improvement is very significant :smile:

Besides the benefits stemming from the features themselves, it would also make contributing easier. Most JavaScript developers have, to at least some extent, moved on to features beyond ES5. And, at least for me, it is very hard to go back. I've gotten so used to const that whenever I see a var I immediately want to change it to a const or a let.

Note that the ES6 features I suggested above are what I consider a conservative minimum. There are many other post ES5 features that I personally like. I think the features suggested are really nice and offer a big bang for the buck.

davidchambers commented 6 years ago

Thanks for your input, Simon. It is much appreciated, as always. :)

I should clarify my defence of ES5 syntax for library code. While it's true that I consider the majority of recent additions to the language to be misguided, I'm not strictly opposed to =>, const, and other well-considered additions.

When writing application code I have an understanding of the target environment or environments and can decide whether it is appropriate to use a particular language feature.

When writing library code I do not know which environments users are targeting, so the only sensible approach is to choose some baseline—I chose ES5—and to distribute code which runs in environments which meet the minimum requirements.

Of course, the source code need not be written in ES5 in order to distribute ES5-compatible code. As you suggest, we could have a build step. For library code, my view is that build steps are more trouble than they're worth. Six years ago I wrote libraries in CoffeeScript; in the years since I have converted those projects to JavaScript to make them easier to maintain and distribute.

ES modules – They can be compiled to other module formats and allows for easy static analysis which most popular bundlers utilize.

This is the argument I find compelling (I'm not bothered by function expressions and var appearing in the source code).

Could you provide a couple of concrete examples of user-facing benefits of Sanctuary using modules?

I'd like to know what you think, @Avaq. I know you have experience with writing code in modules and distributing ES5-compatible bundles.

paldepind commented 6 years ago

Thanks for your input, Simon. It is much appreciated, as always. :)

Thank you :smile:

Of course, the source code need not be written in ES5 in order to distribute ES5-compatible code. As you suggest, we could have a build step.

Yes, indeed. I was not suggesting that Sanctuary should necessarily distribute JS that uses modern features. Babel can be used to compile to ES5.

For library code, my view is that build steps are more trouble than they're worth. Six years ago I wrote libraries in CoffeeScript; in the years since I have converted those projects to JavaScript to make them easier to maintain and distribute.

In my experience using CoffeeScript back in the days was more troublesome than using Babel today is. I think the tools today make build steps in libraries very frictionless. Also, the worst thing that can happen to an ES6 to ES5 build step is that it can be removed in the future when the ES6 features are supported by all environments :smile:

Could you provide a couple of concrete examples of user-facing benefits of Sanctuary using modules?

I think that the internal benefits are more important. But obviously, the core Sanctuary contributors opinions on that are what matters in that regard. Another factor is how future JavaScript developers may not even know about var (just like they probably don't know about with). Or that some may look at the source and mistakenly think that it is outdated or unmaintained.

With regards to user-facing benefits I can only think of two:

I think the latter of these is a very minor benefit though.

masaeedu commented 6 years ago

For projects that do use build steps, having a modular Sanctuary would be a great benefit, since it allows the overall bundle size to be reduced via treeshaking. When you just have one large module with dynamically computed exports, none of Sanctuary can be treeshaken away, which bloats your bundle sizes.

tomkel commented 6 years ago

Agreed with @masaeedu about minifying, Sanctuary's large bundled size prevented my company from adopting it.

Secondary to this, if a small library size is a motivating factor, es6 static imports go a long way to help treeshaking/minifying.

davidchambers commented 6 years ago

I'm not sure it's possible to benefit from tree shaking given the way the library is currently structured. The Sanctuary module is defined by create, which necessarily depends on every Sanctuary function.

I'd love to see an exploratory pull request from someone interested in supporting tree shaking.

paldepind commented 6 years ago

I'm not sure it's possible to benefit from tree shaking given the way the library is currently structured. The Sanctuary module is defined by create, which necessarily depends on every Sanctuary function.

That definitely sounds like something that wouldn't go well with tree-shaking.

Here is one idea that may work. For code like the following:

  function I(x) {
    return x;
  }
  _.I = {
    consts: {},
    types: [a, a],
    impl: I
  };

Is it necessary to add I to _ if I is never used? Maybe the code could be changed into the following:

export const I = (() => {
  const I = (x) => x;
  _.I = { consts: {}, types: [a, a], impl: I };
  return I;
})();

With the code above, if the exported constant I is never imported then the whole IIFE can be tree-shaken away. If I is imported then it will be included in the bundle and the IIFE will be executed and I will be added to _.

davidchambers commented 6 years ago

If this approach works I'll be happy to make these changes, but I need to see a proof of concept first. ;)

davidchambers commented 4 years ago

If there is interest, let's start a new thread to discuss how we might be able to support tree shaking.