@algebraic/type
Advanced tools
Comparing version 1.0.0-alpha.10 to 1.0.0-alpha.11
144
data.js
@@ -0,1 +1,6 @@ | ||
const of = require("./of"); | ||
const any = require("./any"); | ||
const fail = require("./fail"); | ||
const partition = require("@climb/partition"); | ||
const { declaration, fNamed, is, getTypename } = require("./declaration"); | ||
@@ -5,9 +10,27 @@ const { inspect } = require("util"); | ||
const NoDefault = { }; | ||
const has = hasOwnProperty.call.bind(hasOwnProperty); | ||
const { string, ftype } = require("./primitive"); | ||
const { union } = require("./union"); | ||
const EmptyArguments = Object.create(null); | ||
const fNameRegExp = /([^=\s]+)\s*=>/;///(?:^function\s+(?:[^\(\s]*)\(([^\s]+)\))|([^=\s]+)\s*=>/; | ||
const fNameParse = f => fNameRegExp.exec(f + "")[1]; | ||
const fWithDefault = definition => isArray(definition) ? | ||
definition : [definition, NoDefault]; | ||
const fParseMap = farray => farray.map(f => [fNameParse(f), fWithDefault(f())]); | ||
const fieldFromDeclaration = (function () | ||
{ | ||
const fNameRegExp = /(?:^\(\[([^\]]+)\]\))|([^=\s]+)\s*=>/; | ||
const fNameParse = f => fNameRegExp.exec(f + ""); | ||
const fWithDefault = definition => Array.isArray(definition) ? | ||
(([T, value]) => [T, field.init.default({ value })])(definition) : | ||
[definition, field.init.none]; | ||
const fFromArrowFunction = (f, [, computed, set] = fNameParse(f)) => | ||
set ? | ||
((name, [type, init]) => field({ type, name, init })) | ||
(set, fWithDefault(f())) : | ||
((name, [type, compute]) => | ||
field({ type, name, init: field.init.computed({ compute }) })) | ||
(computed, f([])); | ||
return declaration => is(field.declare, declaration) ? | ||
declaration.create() : fFromArrowFunction(declaration); | ||
})(); | ||
const writable = false; | ||
@@ -18,9 +41,12 @@ const enumerable = true; | ||
const GetFieldsSymbol = Symbol("GetFields"); | ||
const DataMetadata = Symbol("data.metadata"); | ||
const cached = f => (cached => () => cached ? | ||
cached.value : (cached = { value: f() }).value) | ||
(false); | ||
exports.data = declaration(function data (type, fieldDefinitions) | ||
const data = declaration(function data (type, fieldDeclarations) | ||
{ | ||
const typename = getTypename(type); | ||
if (fieldDefinitions.length === 0) | ||
if (fieldDeclarations.length === 0) | ||
{ | ||
@@ -36,4 +62,6 @@ const create = fNamed(`[create ${typename}]`, function () | ||
const deserialize = () => type; | ||
const empty = () => []; | ||
type[GetFieldsSymbol] = () => []; | ||
type[DataMetadata] = | ||
{ fields: empty, fieldsCompiled: empty, fieldDeclarations }; | ||
@@ -43,33 +71,41 @@ return { is, create, serialize, deserialize }; | ||
// Legacy | ||
let children = false; | ||
const getChildren = () => children || (children = fParseMap(fieldDefinitions)); | ||
const create = fNamed(`[create ${typename}]`, function (fields) | ||
const getChildren = () => []/*children || (children = | ||
fieldDefinitions.map(field.compile) | ||
.map(field => [field.name, [field.type, field.init]]));*/ | ||
const fields = cached(() => fieldsCompiled().map(field.fromCompiled)); | ||
const fieldsCompiled = cached(() => field.compile(fieldDeclarations)); | ||
const toFieldDeclarations = cached(() => | ||
fieldDeclarations.map(field.toFieldDeclaration)); | ||
const create = fNamed(`[create ${typename}]`, function (...args) | ||
{ | ||
if (!(this instanceof type)) | ||
return new type(fields); | ||
const values = args.length <= 0 ? EmptyArguments : args[0]; | ||
if (!fields) | ||
throw TypeError(`${typename} cannot be created without any fields.`); | ||
if (values instanceof type) | ||
return values; | ||
for (const [property, [child, defaultValue]] of getChildren()) | ||
{ | ||
const value = hasOwnProperty.call(fields, property) ? | ||
fields[property] : defaultValue; | ||
if (!(this instanceof type)) | ||
return new type(values); | ||
if (value === NoDefault) | ||
throw TypeError( | ||
`${typename} constructor requires field "${property}"`); | ||
const mutable = { ...values }; | ||
const compiled = fieldsCompiled(); | ||
const uncomputed = initialize(typename, compiled.uncomputed, mutable); | ||
const computed = compiled.computed.length <= 0 ? | ||
[] : | ||
initialize(typename, | ||
compiled.computed, | ||
Object.fromEntries(uncomputed)); | ||
if (!declaration.is(child, value)) | ||
throw TypeError( | ||
`${typename} constructor passed field ` + | ||
`"${property}" of wrong type. Expected type ` + | ||
`${getTypename(child)} but got ${value}.`); | ||
uncomputed.map(([name, value]) => defineProperty(this, name, | ||
{ value, writable, enumerable, configurable })); | ||
computed.map(([name, value]) => defineProperty(this, name, | ||
{ value, writable, enumerable, configurable })); | ||
defineProperty(this, property, | ||
{ value, writable, enumerable, configurable }); | ||
} | ||
return this; | ||
}); | ||
type.prototype.toString = function () { return inspect(this) }; | ||
type[GetFieldsSymbol] = getChildren; | ||
type[DataMetadata] = { fields, fieldsCompiled, toFieldDeclarations }; | ||
@@ -83,7 +119,40 @@ const is = value => value instanceof type; | ||
exports.data.fields = function (type) | ||
const initialize = (function () | ||
{ | ||
return type[GetFieldsSymbol]().map(([name, [type]]) => [name, type]); | ||
} | ||
const toString = value => | ||
typeof value === "function" ? | ||
value + "" : | ||
JSON.stringify(value); | ||
const typecheck = (owner, name, { expected, value }) => | ||
`${owner} constructor passed value for field "${name}" of wrong ` + | ||
`type. Expected type ${getTypename(expected)} but got ` + | ||
`${toString(value)}.`; | ||
const missing = (owner, name) => | ||
`${owner} constructor requires field "${name}"`; | ||
const message = (owner, name, error) => | ||
is (field.error.missing, error) ? | ||
missing(owner, name, error) : | ||
typecheck(owner, name, error); | ||
const { assign } = Object; | ||
// Should we just return values here? We should seal/freeze it too. | ||
return (typename, fields, values) => fields | ||
.reduce(([values, pairs], [name, [initialize]]) => | ||
(([success, value]) => !success ? | ||
fail.type(message(typename, name, value)) : | ||
[assign(values, { [name]: value }), | ||
(pairs.push([name, value]), pairs)]) | ||
(initialize(values, name)), | ||
[values, []])[1]; | ||
})(); | ||
module.exports.data = data; | ||
const field = require("./field"); | ||
data.always = value => [of(value), () => value]; | ||
data.field = field; | ||
data.fields = type => type[DataMetadata].fields(); | ||
data.fieldDeclarations = type => type[DataMetadata].toFieldDeclarations(); | ||
function toSerialize(typename, getChildren) | ||
@@ -104,8 +173,1 @@ { | ||
} | ||
/* | ||
const fCreate = (f, properties) => | ||
(Object.keys(properties) | ||
.forEach(key => defineProperty(f, key, { value: properties[key] }), f); | ||
*/ | ||
@@ -72,3 +72,3 @@ const typenameStack = []; | ||
{ | ||
const asTypename = object => !!object ? getTypename(object) : object; | ||
const asTypename = object => !!object ? getTypename(object) || object + "" : object; | ||
@@ -98,2 +98,4 @@ return args.reduce((typename, arg, index) => | ||
module.exports.declare = declare; | ||
module.exports.IsSymbol = IsSymbol; | ||
module.exports.UUIDSymbol = UUIDSymbol; | ||
@@ -126,3 +128,3 @@ function getTypename(type) | ||
return definedIs ? definedIs(value) : value instanceof type; | ||
return definedIs ? definedIs(value) : value && value instanceof type; | ||
} | ||
@@ -129,0 +131,0 @@ |
10
maybe.js
const { parameterized } = require("./parameterized"); | ||
const { data } = require("./data"); | ||
const { union } = require("./union"); | ||
const union = require("./union"); | ||
exports.Maybe = parameterized (T => | ||
union `Maybe<${T}>` ( | ||
T, | ||
data `Nothing` () ) ); | ||
module.exports = parameterized (T => | ||
union `maybe <${T}>` ( | ||
data `just` (value => T), | ||
data `nothing` () ) ); |
{ | ||
"name": "@algebraic/type", | ||
"version": "1.0.0-alpha.10", | ||
"version": "1.0.0-alpha.11", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "type.js", |
@@ -1,2 +0,6 @@ | ||
const { getUUID, getTypename } = require("./declaration"); | ||
const of = require("./of"); | ||
const fail = require("./fail"); | ||
const has = hasOwnProperty.call.bind(hasOwnProperty); | ||
const { getUUID, getTypename, IsSymbol, is } = require("./declaration"); | ||
const { stringify } = JSON; | ||
@@ -6,2 +10,3 @@ | ||
const ParametersSymbol = Symbol("ParametersSymbol"); | ||
const Length = Symbol("Length"); | ||
@@ -18,11 +23,13 @@ | ||
const length = internalTypeConstructor.length; | ||
return function typeConstructor(...types) | ||
const typeConstructor = function typeConstructor(...types) | ||
{ | ||
if (types.length !== length) | ||
if (length !== 0 && types.length !== length) | ||
throw TypeError( | ||
`Type constructor takes ${length} types ` + | ||
`but got only ${types.length}` + internalTypeConstructor); | ||
`but got only ${types.length}`); | ||
const UUID = stringify(types.map(type => getUUID(type))); | ||
const maybeUUID = stringify(types.map(type => getUUID(type))); | ||
const UUID = maybeUUID === "[null]" ? | ||
stringify(types.map(type => type + "")) : | ||
maybeUUID; | ||
const existing = cache[UUID]; | ||
@@ -40,2 +47,7 @@ | ||
}; | ||
typeConstructor[Length] = length; | ||
typeConstructor[IsSymbol] = value => valueIs(typeConstructor, value); | ||
return typeConstructor; | ||
}; | ||
@@ -45,2 +57,29 @@ | ||
// This returns true if, given P<T1,T2,...,TN>: | ||
// type.of(value) is of *some* P | ||
// type.of(value) is of P[type.of(value).parameters] | ||
// The second case is for example: | ||
// P = paramaterized(T => union (X<T>, Y<T>)) | ||
// X<T> should intuitively return true for P, since X<T> will for P<T>. | ||
function valueIs(typeConstructor, value) | ||
{ | ||
const type = of(value); | ||
const isParameterized = has(type, ParametersSymbol); | ||
if (!isParameterized) | ||
return false; | ||
if (type[TypeConstructorSymbol] === typeConstructor) | ||
return true; | ||
const length = typeConstructor[Length]; | ||
const parameters = type[ParametersSymbol]; | ||
// Can we parameterize with these? | ||
if (length !== 0 && length !== parameters.length) | ||
return false; | ||
return is (typeConstructor(...parameters), value) | ||
} | ||
parameterized.is = function (typeConstructor, type) | ||
@@ -53,21 +92,11 @@ { | ||
{ | ||
if (!value) | ||
return false; | ||
const type = Object.getPrototypeOf(value).constructor; | ||
return parameterized.is(typeConstructor, type); | ||
return !value && parameterized.is(typeConstructor, of(value)); | ||
} | ||
parameterized.parameters = function (type) | ||
parameterized.parameters = function (valueOrType) | ||
{ | ||
const parameters = type && | ||
(type[ParametersSymbol] || | ||
Object.getPrototypeOf(type).constructor[ParametersSymbol]); | ||
if (parameters) | ||
return parameters; | ||
throw TypeError(`parameters was passed ${type}.`); | ||
return valueOrType ? | ||
(valueOrType[ParametersSymbol] || of(valueOrType)[ParametersSymbol]) : | ||
fail.type(`Parameters was passed ${type}.`); | ||
} | ||
@@ -56,3 +56,3 @@ const { declaration, declare, getTypename, fNamed } = require("./declaration"); | ||
typename: "[primitive null]", | ||
is: fNamed("[is null]", value => value === "null"), | ||
is: fNamed("[is null]", value => value === null), | ||
}), | ||
@@ -68,3 +68,4 @@ | ||
[JSON.stringify, true], | ||
JSON.parse), | ||
JSON.parse) | ||
// λ: parameterized (...Ts => primitives.ftype) | ||
} | ||
@@ -71,0 +72,0 @@ |
17
type.js
@@ -1,7 +0,10 @@ | ||
module.exports = | ||
const type = | ||
{ | ||
always: value => [type.of(value), () => value], | ||
any: require("./any"), | ||
array: require("./array"), | ||
of: require("./of"), | ||
...require("./declaration"), | ||
...require("./data"), | ||
...require("./union"), | ||
union: require("./union"), | ||
...require("./primitive"), | ||
@@ -11,3 +14,9 @@ ...require("./serialize"), | ||
...require("./parameterized"), | ||
...require("./maybe") | ||
maybe: require("./maybe"), | ||
nullable: require("./nullable"), | ||
or: require("./or"), | ||
result: require("./result") | ||
}; | ||
module.exports = type; | ||
module.exports.type = type; |
@@ -18,3 +18,3 @@ const { fNamed, declaration, getTypename, getUnscopedTypename, is } = require("./declaration"); | ||
exports.union = declaration(function union (type, declarations) | ||
const union = declaration(function union (type, declarations) | ||
{ | ||
@@ -48,3 +48,7 @@ const named = fParseMap(declarations); | ||
exports.union.components = function (type) | ||
module.exports = union; | ||
module.exports.union = union; | ||
module.exports.union.components = function (type) | ||
{ | ||
@@ -51,0 +55,0 @@ return type[ComponentsSymbol]; |
32845
24
855