sanctuary-js / sanctuary

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

sanctuary-def dependency #327

Open davidchambers opened 7 years ago

davidchambers commented 7 years ago

Sanctuary depends on sanctuary-def, as specified in package.json. Furthermore, Sanctuary is only compatible with certain versions of sanctuary-def. @codedmart recently brought it to my attention on Gitter that the example for create does not work if one uses the current version of Sanctuary (v0.11.1) with the current version of sanctuary-def (v0.9.0).

There are (at least) two things we should consider:

davidchambers commented 7 years ago

Could we use peerDependencies to express the constraint?

  "dependencies": {
    "sanctuary-def": "0.9.0",
    "sanctuary-type-classes": "2.0.1",
    "sanctuary-type-identifiers": "1.0.0"
  },
  "peerDependencies": {
    "sanctuary-def": "0.9.0"
  }
Avaq commented 7 years ago

versioning the two packages such that sanctuary@X.Y.Z always depends on and works with sanctuary-def@X.Y.Z

This could be a handy indicator, but might also be a hassle to maintain and assure.

Could we use peerDependencies to express the constraint?

I've never seen peerDependencies used like that, though I haven't come across the situation where a package is both a dependency and a plugin to another package. I guess it might win us an automatic warning when the user tries to use an incompatible def, but I think it's all a bit messy. It relates the concerns I expressed here.

The problem, I think, is that multiple parties are creating black-box data structures (type definitions) using sanctuary-def and multiple parties need to interpret (call def on) those structures using sanctuary-def. Maybe it's possible for the future to ship the interpreter with "batteries included", eg:

const sanctuary = require('sanctuary');
const $ = require('sanctuary-def');
const env = $.env.concat(sanctuary.env).concat(myEnv);
const def = TYPE_CHECKING ? $.createTypecheckedDef(env) : $.def;

const S = sanctuary.create(def);

sanctuary would have to have a Peer Dependency on sanctuary-def, but not direct dependency. It's compatibility with def would only change if the type signature of def() changes, but not when the internal data structures change. Maybe It's a bad idea. Might be one for Ideas about env ;)

Avaq commented 7 years ago

Ah I just realized what the flaw in my idea above is: In order to provide an env, we would still need sanctuary-def, and we'd still have the "multiple parties with incompatible data-structures"-problem.

Avaq commented 7 years ago

But it does seem to me like removing sanctuary-def from dependencies and keeping it as only a peer-dependency would reduce complexity. If all these parties would do the same thing, you can be sure that the data structures will be compatible.

davidchambers commented 7 years ago

But it does seem to me like removing sanctuary-def from dependencies and keeping it as only a peer-dependency would reduce complexity.

I agree. const S = sanctuary.create(def); seems very nice to me! Let's seriously consider this change once we get v0.12.0 out the door. :)

Avaq commented 7 years ago

Here's what I think we should do:

  1. Separate all of sanctuary into small modules, each with a peer dependency on sanctuary-def (and possibly sanctuary-type-identifiers and other shared dependencies):
    • sanctuary-either to contain the Either type, its Repr, and associated functions
    • sanctuary-maybe to contain the Maybe type, its Repr, and associated functions
    • sanctuary-list for list related functions
    • sanctuary-string for string related functions
    • sanctuary-strmap for the StrMap type, its Repr, and related functions
    • ...more
  2. Create a sanctuary-prelude library which takes from each of the above libs to provide a set of common tools.
  3. The sanctuary library will have a single function. To load sanctuary-prelude and the shared peer dependencies like sanctuary-def, and instantiate the prelude with a default def.

People who want an out-of-the-box experience will use sanctuary, people who want a customized one will use sanctuary-prelude, people who want only parts of the functionality will assemble their own utility belt from the available underlying libs.

This tackles several problems:

  1. "The library is monolithic": Now we can choose what we want to include. Maybe in the future, each lib can even slit up into stuff like sanctuary-maybe-type, sanctuary-maybe-from-nullable. This package structure is "composable" all the way down to sanctuary.
  2. (And this is personal:) "My colleagues are using require('sanctuary') directly, which defeats the point of disabling type-checking": Now, I will depend only on sanctuary-prelude, and provide my own assembly which they have to use.
  3. "My sanctuary-def is not compatible with yours": Since all packages but sanctuary have it as a peer dependency, they will all use the same version and NPM will show it when one is not compatible and should be down or upgraded.
  4. "I want to use my ENV_X to determine whether to check types": The community is now free to provide many Sanctuary assemblies, since it's very easy to set up. Just depend on the compatible sanctuary-* packages and export create(myDef).
rjmk commented 7 years ago

Is it UnaryType that breaks things currently?

rickmed commented 7 years ago

I really don't know the internals of sanctuary so I have no idea what is possible but I assume that each of those packages would need sanctuary-def (78 kb) and sanctuary-type-classes (45kb). Right now sanctuary is 100kb, let's say sanctuary-maybe would me 15kb. Would it make sense to have a separate package of 15kb with 123kb of dependencies? In that case, me as a user, I would prefer to download an es6 modularized sanctuary and tree shake it myself. Other option is to ask if you are downloading a single package probably you are working on a small project and you would not need type checking, so remove type checking on individual packages -if possible.