useless
Version:
Use Less. Do More. JavaScript on steroids.
145 lines (106 loc) • 6.91 kB
JavaScript
"use strict";
const _ = require ('underscore')
_.hasTypeMatch = true
/* Type matching for arbitrary complex structures (TODO: test)
======================================================================== */
Meta.globalTag ('required')
Meta.globalTag ('atom')
$global.const ('$any', _.identity)
_.deferTest (['type', 'type matching'], function () {
$assert (_.omitTypeMismatches ( { '*': $any, foo: $required ('number'), bar: $required ('number') },
{ baz: 'x', foo: 42, bar: 'foo' }),
{ })
$assert (_.omitTypeMismatches ( { foo: { '*': $any } },
{ foo: { bar: 42, baz: 'qux' } }),
{ foo: { bar: 42, baz: 'qux' } })
$assert (_.omitTypeMismatches ( { foo: { bar: $required(42), '*': $any } },
{ foo: { bar: 'foo', baz: 'qux' } }),
{ })
$assert (_.omitTypeMismatches ( [{ foo: $required ('number'), bar: 'number' }],
[{ foo: 42, bar: 42 },
{ foo: 24, },
{ bar: 42 }]), [{ foo: 42, bar: 42 }, { foo: 24 }])
$assert (_.omitTypeMismatches ({ '*': 'number' }, { foo: 42, bar: 42 }), { foo: 42, bar: 42 })
$assert (_.omitTypeMismatches ({ foo: $any }, { foo: 0 }), { foo: 0 }) // there was a bug (any zero value was omitted)
$assert (_.decideType ([]), [])
$assert (_.decideType (42), 'number')
$assert (_.decideType (_.identity), 'function')
$assert (_.decideType ([{ foo: 1 }, { foo: 2 }]), [{ foo: 'number' }])
$assert (_.decideType ([{ foo: 1 }, { bar: 2 }]), [])
$assert (_.decideType ( { foo: { bar: 1 }, foo: { baz: [] } }),
{ foo: { bar: 'number' }, foo: { baz: [] } })
$assert (_.decideType ( { foo: { bar: 1 }, foo: { bar: 2 } }),
{ foo: { bar: 'number' } })
$assert (_.decideType ( { foo: { bar: 1 },
bar: { bar: 2 } }),
{ '*': { bar: 'number' } })
if (_.hasOOP) {
var Type = $prototype ()
$assert (_.decideType ({ x: new Type () }), { x: Type }) }
}, function () {
_.isMeta = function (x) { return (x === $any) || $atom.is (x) || $required.is (x) }
var zip = function (type, value, pred) {
var required = Meta.unwrapAll (_.filter2 (type, $required.is))
var match = _.nonempty (_.zip2 (Meta.unwrapAll (type), value, pred))
if (_.isEmpty (required)) {
return match }
else { var requiredMatch = _.nonempty (_.zip2 (required, value, pred))
var allSatisfied = _.values2 (required).length === _.values2 (requiredMatch).length
return allSatisfied ?
match : _.coerceToEmpty (value) } }
var hyperMatch = _.hyperOperator (_.binary,
function (type_, value, pred) { var type = Meta.unwrap (type_)
if (_.isArray (type)) { // matches [ItemType] → [item, item, ..., N]
if (_.isArray (value)) {
return zip (_.times (value.length, _.constant (type[0])), value, pred) }
else {
return undefined } }
else if (_.isStrictlyObject (type) && type['*']) { // matches { *: .. } → { a: .., b: .., c: .. }
if (_.isStrictlyObject (value)) {
return zip (_.extend ( _.map2 (value, _.constant (type['*'])),
_.omit (type, '*')), value, pred) }
else {
return undefined } }
else {
return zip (type_, value, pred) } })
var typeMatchesValue = function (c, v) { var contract = Meta.unwrap (c)
return (contract === $any) ||
((contract === undefined) && (v === undefined)) ||
(_.isFunction (contract) && (
_.isPrototypeConstructor (contract) ?
_.isTypeOf (contract, v) : // constructor type
(contract (v) === true))) || // test predicate
(typeof v === contract) || // plain JS type
(v === contract) } // constant match
_.mismatches = function (op, contract, value) {
return hyperMatch (contract, value,
function (contract, v) {
return op (contract, v) ? undefined : contract }) }
_.omitMismatches = function (op, contract, value) {
return hyperMatch (contract, value,
function (contract, v) {
return op (contract, v) ? v : undefined }) }
_.typeMismatches = _.partial (_.mismatches, typeMatchesValue)
_.omitTypeMismatches = _.partial (_.omitMismatches, typeMatchesValue)
_.valueMismatches = _.partial (_.mismatches, function (a, b) { return (a === $any) || (b === $any) || (a === b) })
var unifyType = function (value) {
if (_.isArray (value)) {
return _.nonempty ([_.reduce (value.slice (1), function (a, b) { return _.undiff (a, b) }, _.first (value) || undefined)]) }
else if (_.isStrictlyObject (value)) {
var pairs = _.pairs (value)
var unite = _.map ( _.reduce (pairs.slice (1), function (a, b) { return _.undiff (a, b) }, _.first (pairs) || [undefined, undefined]),
_.nonempty)
return (_.isEmpty (unite) || _.isEmpty (unite[1])) ? value : _.fromPairs ([[unite[0] || '*', unite[1]]]) }
else {
return value } }
_.decideType = function (value) {
var operator = _.hyperOperator (_.unary,
function (value, pred) {
if (value && value.constructor && value.constructor.$definition) {
return value.constructor }
return unifyType (_.map2 (value, pred)) })
return operator (value, function (value) {
if (_.isPrototypeInstance (value)) {
return value.constructor }
else {
return _.isEmptyArray (value) ? value : (typeof value) } }) } }) // TODO: fix hyperOperator to remove additional check for []