static-land: Should the spec not define a mechanism to associate instances and types?

Static land replaces prototypes with explicit type dictionaries. While the spec defines how this dictionaries have to be constructed and which rules their static methods have to follow, it says nothing about the mechanism, how a specific instance should be associated with its corresponding type.

Javascript’s prototype system guarantees that each instance of a type is always implicitly associated with the right prototype. There is no such guarantee with explicit type dictionaries. So in a way the spec offers an approach that is even less type safe than the prototype system.

Should the spec not at least point out that a static land compliant implementation must provide a mechanism, which guarantees that an instance can only be used with its corresponding type dict.

Here is an example of such an mechanism (the implementation is not static land compliant). Please note that upper case identifiers represent values wrapped in a functor:

const cons = sym => x => 
 Object.defineProperty([].concat(x), "tag", {value: sym});

const List = {
  tag: Symbol.for("ftor/list"),
  cata: o => X => o[X.tag](X),
  fold: f => X => List.cata({[List.tag]: f}) (X),
  map: f => X => List.fold(
    xs => cons(List.tag) (xs.map(f))
  ) (X)
};

const Maybe = {
  some: Symbol.for("ftor/some"),
  none: Symbol.for("ftor/none"),
  cata: o => X => o[X.tag](X[0]),
  fold: f => g => X => Maybe.cata(
    {[Maybe.some]: f, [Maybe.none]: g}
  ) (X),
  map: f => X => Maybe.fold(
    x => cons(Maybe.some) (f(x))
  ) (K(X)) (X)
};

const K = x => y => x;
const sqr = x => x * x;

const x = cons(List.tag) ([1,2,3]);
const y = cons(Maybe.some) (1);

List.map(sqr) (x); // [1,4,9]
List.map(sqr) (y); // TypeError

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 21 (8 by maintainers)

Most upvoted comments

FL probably doesn’t solve this. But still if someone doesn’t need to solve problems like that, and don’t want to pass dictionaries around, they could rely on the @static-land property.

I mean the approach with @static-land is more limited compared to passing dictionaries explicitly. But it still can be used in cases when these limitations don’t matter so much.

Static land along with a static type checker works well (and Flow is pretty solid when doing functional programming in static-land style).

However chaining without do notation is awkward

const double = (n: number): number => n * 2
const length = (s: string): number => s.length
const inverse = (n: number): Maybe<number> => maybe.fromPredicate((n: number) => n !== 0)(n)

const o1 = maybe.chain(
  inverse, maybe.map(
    double, maybe.map(
      length, Just('hello'))))

vs

const o2 = Just('hello')
  .map(length)
  .map(double)
  .chain(inverse)

Here is where fantasy-land style really shines.

PureScript is great, but it’s hard to introduce in a team.

I’d say that the best balance is mixing FL and ST along with TypeScript or Flow

@rpominov @polytypic, since I started this mess I should probably give a reasonable conclusion:

Static land undoubtedly solves actual problems. But giving up the prototype system isn’t for free, because we lose the remnant of type safety Javascript provides. This might lead to more laborious development of large-scale programs.

There seem to be essentially four alternatives:

  • just deal with it
  • use static land along with a static type checker (MS Typescript seems to be optimized for OO-style though)
  • consider Javascript as a compile target and use purescript (which is essentially Haskell for the web but without lazy eval and an replacement for IO)
  • use FL when possible and SL when necessary

@ivenmarquardt BTW, your example

const All = {
  empty: () => true,
  concat: (x, y) => x && y
};

// type dictionary as a WeakMap

const typeDict = new WeakMap();

typeDict.set(Boolean.prototype, {
  monoid: All
});

const lookup = o => typeDict.get(o.constructor !== undefined 
 ? o.constructor.prototype
 : Reflect.getPrototypeOf(o));

const foldl = xs => xs.reduce(
  lookup(xs[0]).monoid.concat, lookup(xs[0]).monoid.empty()
);

exhibits the number 3 problem solved by Static Land and the problem I linked to: foldMapOf fails with an empty target. Here is what happens if you call foldl with an empty list:

foldl([]);
Uncaught TypeError: Cannot read property 'constructor' of undefined
    at lookup (<anonymous>:1:35)
    at foldl (<anonymous>:2:3)
    at <anonymous>:1:1

This is one of the reasons why I switched to using Static Land in my partial.lenses library (which also implements folds).