typesafe

Help­ers for work­ing with types; spe­cific­ally de­signed to al­low null/​un­defined to be treated the same as any other value.

Source:

Methods

(inner) builder(T, fn) → {*}

Source:

The con­structor of a class into a func­tion.

const as­sert = re­quire('assert');
const { as­sign } = Ob­ject;
const {
  cre­ate­From, curry, builder, as­sertSequenceEquals, type, map,
  ap­ply, Equals,
} = re­quire('fer­rum');

// Rep­res­ents a frac­tion a/​b
class Ra­tional {
  con­structor(nu­mer­ator, de­nom­in­ator) {
    as­sign(this, { nu­mer­ator, de­nom­in­ator });
  }

  [Equals.sym](otr) {
    re­turn this.nu­mer­ator === otr.nu­mer­ator
        && this.de­nom­in­ator === otr.de­nom­in­ator;
  }
};

// I like to provide a static, curry­able new method; builder()
// sets the .length and .name prop­er­ties; which is why I prefer it
// over an ad-hoc con­struc­tion.
Ra­tional.new = curry('Ra­tional.new', builder(Ra­tional));
const Halfs = Ra­tional.new(2);
as­sert.deep­Stric­tEqual(
  Halfs(3),
  cre­ate­From(Ra­tional, { nu­mer­ator: 3, de­nom­in­ator: 2 }));

// This is equi­val­ent to
Ra­tional.new2 = curry('Ra­tional.new2',
  (nu­mer­ator, de­nom­in­ator) =>
    new Ra­tional(nu­mer­ator, de­nom­in­ator));

const par = [
  [3, 4],
  [14, 11],
];
const ref = [
  cre­ate­From(Ra­tional, { nu­mer­ator: 3, de­nom­in­ator: 4 }),
  cre­ate­From(Ra­tional, { nu­mer­ator: 14, de­nom­in­ator: 11 }),
];

// Now you can use this func­tion like any other func­tion
as­sertSequenceEquals(map(par, ap­ply(Ra­tional.new)), ref);
as­sertSequenceEquals(map(par, ap­ply(Ra­tional.new2)), ref);

// Of course you can use this on the fly too – most lib­rar­ies
// wont come with classes fea­tur­ing a .new method.
as­sertSequenceEquals(map(par, ap­ply(builder(Ra­tional))), ref);

// Without builder you would have to write this:
as­sertSequenceEquals(map(par, (args) => new Ra­tional(...args)), ref);

// This func­tion is null and un­defined safe
as­sert.stric­tEqual(builder(type(null))(), null);
as­sert.stric­tEqual(builder(type(un­defined))(), un­defined);

// And since type(null|un­defined) is defined to be the
// iden­tity func­tion
as­sert.stric­tEqual(builder(null)(), null);
as­sert.stric­tEqual(builder(un­defined)(), un­defined);

Ver­sion his­tory

  • 1.9.0 Ini­tial im­ple­ment­a­tion
Parameters:
Name Type Description
T function | null | undefined

The type to get the con­struct off

fn function

The muta­tion pro­ced­ure

Returns:

obj

Type
*

(inner) create(t) → {t}

Source:
See:

In­stan­ti­ate a class by­passing it's con­structor.

cre­ate­From() con­tains a more de­tailed dis­cus­sion of why you would want to use this.

const as­sert = re­quire('assert');
const { cre­ate, type } = re­quire('fer­rum');

class Foo {
  con­structor() {
     as­sert(false, "Un­reach­able!");
  }
};

// Cre­ate by­passes the nor­mal con­structor
const foo = cre­ate(Foo);
as­sert.stric­tEqual(type(foo), Foo);

// Cre­ate() is null and un­defined safe. (Re­mem­ber type(null|un­defined) = null|un­defined)
as­sert.stric­tEqual(cre­ate(type(null)), null);
as­sert.stric­tEqual(cre­ate(type(un­defined)), un­defined);
as­sert.stric­tEqual(cre­ate(null), null);
as­sert.stric­tEqual(cre­ate(un­defined), un­defined);

Ver­sion his­tory

  • 1.9.0 Ini­tial im­ple­ment­a­tion
Parameters:
Name Type Description
t function | null | undefined

The type to con­struct.

Returns:

The in­stance of the given type.

Type
t

(inner) createFrom(t) → {t}

Source:

In­stan­ti­ate a class and set fields, by­passing it's con­structor.

const as­sert = re­quire('assert');
const { cre­ate­From, type } = re­quire('fer­rum');

class Foo {
  con­structor() {
     as­sert(false, "Un­reach­able!");
  }
};

// By­passes the con­structor and sets fields.
const foo = cre­ate­From(Foo, { an­swer: 42 });
as­sert.stric­tEqual(type(foo), Foo);
as­sert.stric­tEqual(foo.an­swer, 42);

// Cre­ate by­passes the nor­mal con­structor const foo = cre­ate(Foo); as­sert.stric­tEqual(type(foo) === Foo);

This can be very use­ful when de­fin­ing mul­tiple con­struct­ors, when by­passing the de­fault con­structor in a method or when the nor­mal con­structor can not be used be­cause con­struc­tion is async.

const as­sert = re­quire('assert');
const { cre­ate­From, curry } = re­quire('fer­rum');
const { as­sign } = Ob­ject;

const { abs } = Math;

const xor = (a, b) => Boolean(a) ^ Boolean(b) ? (a||b) : null;

const gcd = (a, b) => {
  if (a < b)
    re­turn gcd(b, a);
  else if (b === 0)
    re­turn a;
  else
    re­turn gcd(b, a%b);
};

// Rep­res­ents a frac­tion a/​b
class Ra­tional {
  con­structor(nu­mer­ator, de­nom­in­ator) {
    // Nor­mal­ize the frac­tion so we use a nor­mal­ized rep­res­ent­a­tion:
    // The frac­tion is fully re­duced and the sign is stored in the nu­mer­ator
    const n = abs(nu­mer­ator), d = abs(de­nom­in­ator);
    const s = xor(nu­mer­ator<0, de­nom­in­ator<0) ? -1 : +1;
    const g = gcd(n, d);
    as­sign(this, {
      nu­mer­ator: s*n/​g,
      de­nom­in­ator: d/​g,
    });
  }

  mul(otr) {
    // Cir­cum­vent the con­struc­tion as we mul­tiply­ing two nor­mal­ized
    // Ra­tional yields an­other nor­mal­ized Ra­tional; no use wast­ing cycles
    re­turn cre­ate­From(Ra­tional, {
      nu­mer­ator: this.nu­mer­ator * otr.nu­mer­ator,
      de­nom­in­ator: this.de­nom­in­ator * otr.de­nom­in­ator,
    });
  }

  // ... other meth­ods ...
};

Ra­tional.new = curry('Ra­tional.new', (num, den) =>
  new Ra­tional(num, den));

// Provide a con­structor from int; again we know this
// is nor­mal­ized; no use wast­ing cycles
Ra­tional.fro­mIn­teger = (i) => cre­ate­From(Ra­tional, {
  nu­mer­ator: i,
  de­nom­in­ator: 1,
});

// Fi­nally we can short­cut the con­structor while test­ing
as­sert.deep­Stric­tEqual(
  new Ra­tional(15, 3),
  cre­ate­From(Ra­tional, { nu­mer­ator: 5, de­nom­in­ator: 1 }));
as­sert.deep­Stric­tEqual(
  Ra­tional.new(6, 8),
  cre­ate­From(Ra­tional, { nu­mer­ator: 3, de­nom­in­ator: 4 }));
as­sert.deep­Stric­tEqual(
  Ra­tional.new(6, -8),
  cre­ate­From(Ra­tional, { nu­mer­ator: -3, de­nom­in­ator: 4 }));
as­sert.deep­Stric­tEqual(
  Ra­tional.new(6, -8).mul(Ra­tional.new(-3, 2)),
  cre­ate­From(Ra­tional, { nu­mer­ator: 9, de­nom­in­ator: 8 }));

Fi­nally, it can be used to cre­ate classes with async con­struct­ors us­ing this pat­tern:

const as­sert = re­quire('assert');
const { cre­ate­From, type } = re­quire('fer­rum');

class MyData­base {
  con­structor() {
    as­sert(false, "Use await MyData­base.new(), not new MyData­base()")
  }
};

MyData­base.new = async (file) => {
  // ... do io ...
  re­turn cre­ate­From(MyData­base, { file });
};

const main = async () => {
  const p = MyData­base.new("ford/​pre­fect");
  as­sert.stric­tEqual(type(p), Prom­ise);
  as­sert.deep­Stric­tEqual(await p, cre­ate­From(MyData­base, { file: "ford/​pre­fect" }));
};

await main();

Cre­ate from is null and un­defined safe.

const as­sert = re­quire('assert');
const { cre­ate­From, type } = re­quire('fer­rum');

as­sert.stric­tEqual(cre­ate­From(type(null), {}), null);
as­sert.stric­tEqual(cre­ate­From(type(un­defined), {}), un­defined);

// type() is defined to be the iden­tity func­tion for null and un­defined

as­sert.stric­tEqual(cre­ate­From(null, {}), null);
as­sert.stric­tEqual(cre­ate­From(un­defined, {}), un­defined);

// Do not sup­ply data! Since data can­not be as­signed to null and
// un­defined, this will throw
as­sert.throws(() => cre­ate­From(null, { foo: 42 }));
as­sert.throws(() => cre­ate­From(un­defined, { foo: 42 }));

Ver­sion his­tory

  • 1.9.0 Ini­tial im­ple­ment­a­tion
Parameters:
Name Type Description
t function | null | undefined

The type to con­struct.

Returns:

The in­stance of the given type.

Type
t

(inner) ifdef(v, fn)

Source:

Ap­ply the given func­tion to the value only if the value is defined (not null or un­defined).

This ba­sic­ally im­ple­ments Op­tional se­mantics us­ing null/​un­defined.

const { stric­tEqual: as­sertIs } = re­quire('assert');
const { as­sertSequenceEquals, plus,  is­def, if­def, map } = re­quire('fer­rum');

const o = {
  foo: 42
};

as­sertIs(if­def(o['foo'], plus(2)), 44);
as­sertIs(if­def(o['bar'], plus(2)), un­defined);

// This is par­tic­u­larly use­ful for map or curry
as­sertSequenceEquals(
  map([1, 2, null, 3], if­def(x => x*3)),
  [3,6,null,9]);

// Without if­def the pipe above would have to be manu­ally writ­ten,
// which is a bit harder to read
as­sertSequenceEquals(
  map([1, 2, null, 3], (x) => is­def(x) ? x*3 : x),
  [3, 6, null, 9]);
Parameters:
Name Type Description
v T
fn function
Returns:

null | un­defined | typeof(fn())

(inner) isdef(v) → {Boolean}

Source:

Checks whether a value is null or un­defined

const as­sert = re­quire('assert');
const { is­def } = re­quire('fer­rum');

as­sert(is­def(0));
as­sert(is­def(false));
as­sert(!is­def(null));
as­sert(!is­def(un­defined));

This func­tion con­siders all val­ues that are not null and not un­defined to be defined.

Parameters:
Name Type Description
v T
Returns:
Type
Boolean

(inner) isPrimitive(v) → {Boolean}

Source:

Test if a value is prim­it­ive

const { stric­tEqual: as­sertIs } = re­quire('assert');
const { isPrim­it­ive } = re­quire('fer­rum');

as­sertIs(isPrim­it­ive(null),      true);
as­sertIs(isPrim­it­ive(un­defined), true);
as­sertIs(isPrim­it­ive(true),      true);
as­sertIs(isPrim­it­ive(false),     true);
as­sertIs(isPrim­it­ive(Sym­bol()),  true);
as­sertIs(isPrim­it­ive(""),        true);
as­sertIs(isPrim­it­ive(42),        true);
as­sertIs(isPrim­it­ive({}),        false);
as­sertIs(isPrim­it­ive(new Num­ber(42)), false);
Parameters:
Name Type Description
v T
Returns:
Type
Boolean

(inner) type(v) → {function|null|undefined}

Source:

De­term­ine type of an ob­ject.

const { stric­tEqual: as­sertIs } = re­quire('assert');
const { type } = re­quire('fer­rum');

class Bar {};

as­sertIs(type(null),           null);
as­sertIs(type(un­defined),      un­defined);
as­sertIs(type({}),             Ob­ject);
as­sertIs(type(42),             Num­ber);
as­sertIs(type(new Num­ber(42)), Num­ber);
as­sertIs(type(new Bar()),      Bar);

// The usual strategy to get the type is this
as­sertIs(new Bar().con­structor, Bar);

// Which fails for null and un­defined...
//null.con­structor
// Thrown:
// TypeEr­ror: Can­not read prop­erty 'con­struct­or' of null

Like obj.con­structor, but won't fail for null/​un­defined and just re­turns the value it­self for those. This is a use­ful fea­ture for code that is sup­posed to be null/​un­defined-safe since those need not be spe­cial cased.

Parameters:
Name Type Description
v T
Returns:

The type of the given para­meter

Type
function | null | undefined

(inner) typename(The) → {String}

Source:

Given a type, de­term­ine it's name.

const { stric­tEqual: as­sertIs } = re­quire('assert');
const { type, type­name } = re­quire('fer­rum');

class Bar {};

as­sertIs(type­name(null),            "null");
as­sertIs(type­name(un­defined),       "un­defined");
as­sertIs(type­name(type(null)),      "null");
as­sertIs(type­name(type(un­defined)), "un­defined");
as­sertIs(type­name(type({})),        "Ob­ject");
as­sertIs(type­name(type(42)),        "Num­ber");
as­sertIs(type­name(Bar),             "Bar");

// The usual strategy to get the name of a value's type is this
as­sertIs(new Bar().con­structor.name, "Bar");

// But this ob­vi­ously fails for null & un­defined
//null.con­structor.name
//null.name // still throws

This is use­ful as a re­place­ment for val.con­structor.name, since this can deal with null and un­defined.

Parameters:
Name Type Description
The function | null | undefined

type to get the name of

Returns:
Type
String