Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have proposal wants be amount patterns rather than just amounts #1905

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 110 additions & 6 deletions packages/ERTP/src/amountMath.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// @ts-check

import { assert, details } from '@agoric/assert';
import { assert, details as d } from '@agoric/assert';

import { mustBeComparable } from '@agoric/same-structure';
import {
mustBeComparable,
patternKindOf,
STAR_PATTERN,
isGround,
matches,
} from '@agoric/same-structure';

import './types';
import natMathHelpers from './mathHelpers/natMathHelpers';
Expand Down Expand Up @@ -87,7 +93,7 @@ function makeAmountMath(brand, amountMathKind) {
const helpers = mathHelpers[amountMathKind];
assert(
helpers !== undefined,
details`unrecognized amountMathKind: ${amountMathKind}`,
d`unrecognized amountMathKind: ${amountMathKind}`,
);

// Cache the amount if we can.
Expand All @@ -105,6 +111,10 @@ function makeAmountMath(brand, amountMathKind) {
* @returns {Amount}
*/
make: allegedValue => {
assert(
isGround(allegedValue),
d`allegedValue ${allegedValue} must not be a non-ground pattern`,
);
const value = helpers.doCoerce(allegedValue);
const amount = harden({ brand, value });
cache.add(amount);
Expand All @@ -126,11 +136,11 @@ function makeAmountMath(brand, amountMathKind) {
const { brand: allegedBrand, value } = allegedAmount;
assert(
allegedBrand !== undefined,
details`The brand in allegedAmount ${allegedAmount} is undefined. Did you pass a value rather than an amount?`,
d`The brand in allegedAmount ${allegedAmount} is undefined. Did you pass a value rather than an amount?`,
);
assert(
brand === allegedBrand,
details`The brand in the allegedAmount ${allegedAmount} in 'coerce' didn't match the amountMath brand ${brand}.`,
d`The brand in the allegedAmount ${allegedAmount} in 'coerce' didn't match the amountMath brand ${brand}.`,
);
// Will throw on inappropriate value
return amountMath.make(value);
Expand Down Expand Up @@ -181,11 +191,105 @@ function makeAmountMath(brand, amountMathKind) {
amountMath.getValue(rightAmount),
),
),

/**
* TODO explain.
*
* @param {ValuePattern} valuePattern
* @returns {AmountPattern}
*/
makePattern: valuePattern => {
mustBeComparable(valuePattern);
if (isGround(valuePattern)) {
return amountMath.make(valuePattern);
}
return harden({ brand, value: valuePattern });
},

/**
* TODO explain.
*
* @returns {AmountPattern}
*/
makeStarPattern: () => {
return amountMath.makePattern(STAR_PATTERN);
},

/**
* TODO explain.
*
* @param {AmountPattern} allegedAmountPattern
* @returns {AmountPattern} or throws if invalid
*/
coercePattern: allegedAmountPattern => {
const { brand: allegedBrand, value: valuePattern } = allegedAmountPattern;
assert(
allegedBrand !== undefined,
d`alleged brand is undefined. Did you pass a value pattern rather than an amount pattern?`,
);
assert(
brand === allegedBrand,
d`the brand in the allegedAmountPattern in 'coerce' didn't match the amountMath brand`,
);
// Will throw on inappropriate value
return amountMath.makePattern(valuePattern);
},

// TODO explain.
getValuePattern: amountPattern =>
amountMath.coercePattern(amountPattern).value,

frugalSplit: (pattern, specimen) => {
if (isGround(pattern)) {
if (amountMath.isGTE(specimen, pattern)) {
return harden({
matched: pattern,
change: amountMath.subtract(specimen, pattern),
});
}
return undefined;
}
const patternKind = patternKindOf(pattern);
switch (patternKind) {
case '*': {
return harden({
// eslint-disable-next-line no-use-before-define
matched: empty,
change: specimen,
});
}
default: {
const split = helpers.doFrugalSplit(
amountMath.getValuePattern(pattern),
amountMath.getValue(specimen),
);
if (split === undefined) {
return undefined;
}
const { matched: valueMatched, change: valueChange } = split;
const matched = amountMath.make(valueMatched);
const change = amountMath.make(valueChange);
assert(
matches(pattern, matched),
d`Internal: doFrugalSplit algorithm violated matching invariant`,
);
assert(
amountMath.isEqual(amountMath.add(matched, change), specimen),
d`Internal: doFrugalSplit algorithm violated conservation invariant`,
);
return harden({ matched, change });
}
}
},
satisfies: (pattern, specimen) => {
// TODO should somehow enable individual MathKinds to override for
// a more efficient test that gives the same answer.
return undefined !== amountMath.frugalSplit(pattern, specimen);
},
});
const empty = amountMath.make(helpers.doGetEmpty());
return amountMath;
}

harden(makeAmountMath);

export { makeAmountMath };
5 changes: 5 additions & 0 deletions packages/ERTP/src/mathHelpers/natMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check

import Nat from '@agoric/nat';
import { assert, details as d } from '@agoric/assert';

import '../types';

Expand All @@ -26,6 +27,10 @@ const natMathHelpers = harden({
doIsEqual: (left, right) => left === right,
doAdd: (left, right) => Nat(left + right),
doSubtract: (left, right) => Nat(left - right),

doFrugalSplit: (pattern, _specimen) => {
throw assert.fail(d`Unexpected nat pattern ${pattern}`);
},
});

harden(natMathHelpers);
Expand Down
8 changes: 5 additions & 3 deletions packages/ERTP/src/mathHelpers/setMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @ts-check

import { passStyleOf } from '@agoric/marshal';
import { assert, details } from '@agoric/assert';
import { assert, details as d } from '@agoric/assert';
import { sameStructure } from '@agoric/same-structure';
import { doFrugalSplit } from './strSetMathHelpers';

import '../types';

Expand Down Expand Up @@ -43,7 +44,7 @@ const checkForDupes = buckets => {
for (let j = i + 1; j < maybeMatches.length; j += 1) {
assert(
!sameStructure(maybeMatches[i], maybeMatches[j]),
details`value has duplicates: ${maybeMatches[i]} and ${maybeMatches[j]}`,
d`value has duplicates: ${maybeMatches[i]} and ${maybeMatches[j]}`,
);
}
}
Expand Down Expand Up @@ -92,12 +93,13 @@ const setMathHelpers = harden({
right.forEach(rightElem => {
assert(
hasElement(leftBuckets, rightElem),
details`right element ${rightElem} was not in left`,
d`right element ${rightElem} was not in left`,
);
});
const leftElemNotInRight = leftElem => !hasElement(rightBuckets, leftElem);
return harden(left.filter(leftElemNotInRight));
},
doFrugalSplit,
});

harden(setMathHelpers);
Expand Down
74 changes: 67 additions & 7 deletions packages/ERTP/src/mathHelpers/strSetMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,77 @@
// @ts-check

import { passStyleOf } from '@agoric/marshal';
import { assert, details } from '@agoric/assert';
import { assert, details as d, q } from '@agoric/assert';
import { patternKindOf, matches } from '@agoric/same-structure';

const { entries } = Object;

const identity = harden([]);

const checkForDupes = list => {
const set = new Set(list);
assert(set.size === list.length, details`value has duplicates: ${list}`);
assert(set.size === list.length, d`value has duplicates: ${list}`);
};

/**
* If the pattern is an array of patterns, then we define frugal according
* to a first-fit rule, where the order of elements in the pattern and the
* specimen matter. The most frugal match happens when the pattern is ordered
* from most specific to most general. However, this is advice to the caller,
* not something we either enforce or normalize to. As a result, a frugalSplit
* can fail even if each of the sub patterns could have been matched with
* distinct elements.
*
* For each element of the specimen, from left to right, we see if
* it matches a sub pattern of pattern, from left to right. If so,
* that sub pattern is satisfied and removed from the sub patterns yet
* to be satisfied. The element is moved into matched or change accordingly.
*
* This algorithm is generic across mathKind values that represent a set
* as an array of elements, so that it can be reused by other mathKinds.
*
* @param {ValuePattern} pattern
* @param {Value} specimen
* @returns {ValueSplit | undefined}
*/
const doFrugalSplit = (pattern, specimen) => {
const patternKind = patternKindOf(pattern);
switch (patternKind) {
case undefined: {
if (!Array.isArray(pattern)) {
return undefined;
}
const hungryPatterns = [...pattern];
const matched = [];
const change = [];
for (const element of specimen) {
let found = false;
for (const [iStr, subPattern] of entries(hungryPatterns)) {
if (matches(subPattern, element)) {
matched.push(element);
hungryPatterns.splice(+iStr, 1);
found = true;
break;
}
}
if (!found) {
change.push(element);
}
}
if (hungryPatterns.length >= 1) {
return undefined;
}
return harden({ matched, change });
}
default: {
throw assert.fail(d`Unexpected patternKind ${q(patternKind)}`);
}
}
};

harden(doFrugalSplit);
export { doFrugalSplit };

/**
* Operations for arrays with unique string elements. More information
* about these assets might be provided by some other mechanism, such as
Expand Down Expand Up @@ -43,10 +105,7 @@ const strSetMathHelpers = harden({
doAdd: (left, right) => {
const union = new Set(left);
const addToUnion = elem => {
assert(
!union.has(elem),
details`left and right have same element ${elem}`,
);
assert(!union.has(elem), d`left and right have same element ${elem}`);
union.add(elem);
};
right.forEach(addToUnion);
Expand All @@ -58,10 +117,11 @@ const strSetMathHelpers = harden({
const allRemovedCorrectly = right.every(remove);
assert(
allRemovedCorrectly,
details`some of the elements in right (${right}) were not present in left (${left})`,
d`some of the elements in right (${right}) were not present in left (${left})`,
);
return harden(Array.from(leftSet));
},
doFrugalSplit,
});

harden(strSetMathHelpers);
Expand Down
Loading