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

Try to solve method composition with proxy objects #1240

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
23 changes: 22 additions & 1 deletion packages/core/bench/pattern.bench.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, bench } from 'vitest';

import { calculateTactus, sequence, stack } from '../index.mjs';
import { calculateTactus, sequence, stack, fast, note } from '../index.mjs';

const pat64 = sequence(...Array(64).keys());

Expand Down Expand Up @@ -44,3 +44,24 @@ describe('stack', () => {
);
});
calculateTactus(true);

describe('proxify', () => {
bench(
'proxied',
() => {
note(pat64).sound('folkharp').every(3, fast(2).speed(2).rev()).fast(16).queryArc(0, 3);
},
{ time: 1000 },
);
bench(
'unproxied',
() => {
note(pat64)
.sound('folkharp')
.every(3, (x) => x.fast(2).speed(2).rev())
.fast(16)
.queryArc(0, 3);
},
{ time: 1000 },
);
});
1 change: 1 addition & 0 deletions packages/core/controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function createParam(names) {
}
return this.set(func(...pats));
};
setter._Param = true;
Pattern.prototype[name] = setter;
return func;
}
Expand Down
96 changes: 66 additions & 30 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1488,36 +1488,71 @@ function _sequenceCount(x) {
return [reify(x), 1];
}

export const mask = curry((a, b) => reify(b).mask(a));
export const struct = curry((a, b) => reify(b).struct(a));
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
const _proxify_handler = {
get: function (target, prop, receiver) {
// chain pattern methods
if (prop in Pattern.prototype) {
return (...args) => new Proxy((pat) => target(pat)[prop](...args), _proxify_handler);
}
return Reflect.get(target, prop);
},
};
const proxify = (func) => new Proxy(func, _proxify_handler);
const curryProxified = (func) => curry(func, null, func.length, proxify);

export const mask = curryProxified((a, b) => reify(b).mask(a));
export const struct = curryProxified((a, b) => reify(b).struct(a));
export const superimpose = curryProxified((a, b) => reify(b).superimpose(...a));

// operators
export const set = curry((a, b) => reify(b).set(a));
export const keep = curry((a, b) => reify(b).keep(a));
export const keepif = curry((a, b) => reify(b).keepif(a));
export const add = curry((a, b) => reify(b).add(a));
export const sub = curry((a, b) => reify(b).sub(a));
export const mul = curry((a, b) => reify(b).mul(a));
export const div = curry((a, b) => reify(b).div(a));
export const mod = curry((a, b) => reify(b).mod(a));
export const pow = curry((a, b) => reify(b).pow(a));
export const band = curry((a, b) => reify(b).band(a));
export const bor = curry((a, b) => reify(b).bor(a));
export const bxor = curry((a, b) => reify(b).bxor(a));
export const blshift = curry((a, b) => reify(b).blshift(a));
export const brshift = curry((a, b) => reify(b).brshift(a));
export const lt = curry((a, b) => reify(b).lt(a));
export const gt = curry((a, b) => reify(b).gt(a));
export const lte = curry((a, b) => reify(b).lte(a));
export const gte = curry((a, b) => reify(b).gte(a));
export const eq = curry((a, b) => reify(b).eq(a));
export const eqt = curry((a, b) => reify(b).eqt(a));
export const ne = curry((a, b) => reify(b).ne(a));
export const net = curry((a, b) => reify(b).net(a));
export const and = curry((a, b) => reify(b).and(a));
export const or = curry((a, b) => reify(b).or(a));
export const func = curry((a, b) => reify(b).func(a));
const opFunc = function (name) {
const handler = {
// magic to support things like set.n(3)
get: function (target, prop, receiver) {
if (prop in Pattern.prototype && '_Param' in Pattern.prototype[prop]) {
return (val) =>
proxify((pat) =>
target(
reify(val).fmap((x) => ({ [prop]: x })),
pat,
),
);
}
return Reflect.get(target, prop);
},
};

const func = curryProxified((a, b) => reify(b)[name](a));
return new Proxy(func, handler);
};

export const set = opFunc('set');
export const keep = opFunc('keep');
export const keepif = opFunc('keepif');
export const add = opFunc('add');
export const sub = opFunc('sub');
export const mul = opFunc('mul');
export const div = opFunc('div');
export const mod = opFunc('mod');
export const pow = opFunc('pow');
export const band = opFunc('band');
export const bor = opFunc('bor');
export const bxor = opFunc('bxor');
export const blshift = opFunc('blshift');
export const brshift = opFunc('brshift');
export const lt = opFunc('lt');
export const gt = opFunc('gt');
export const lte = opFunc('lte');
export const gte = opFunc('gte');
export const eq = opFunc('eq');
export const eqt = opFunc('eqt');
export const ne = opFunc('ne');
export const net = opFunc('net');
export const and = opFunc('and');
export const or = opFunc('or');
export const func = opFunc('func');

export const hitch = proxify((x) => x);

/**
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
Expand Down Expand Up @@ -1550,6 +1585,7 @@ export function register(name, func, patternify = true, preserveTactus = false,
const firstArgs = args.slice(0, -1);

if (firstArgs.every((arg) => arg.__pure != undefined)) {
// All the args are 'pure', so we can treat them as unpatterned
const pureArgs = firstArgs.map((arg) => arg.__pure);
const pureLocs = firstArgs.filter((arg) => arg.__pure_loc).map((arg) => arg.__pure_loc);
result = func(...pureArgs, pat);
Expand All @@ -1563,7 +1599,7 @@ export function register(name, func, patternify = true, preserveTactus = false,
let mapFn = (...args) => {
return func(...args, pat);
};
mapFn = curry(mapFn, null, arity - 1);
mapFn = curry(mapFn, null, arity - 1, proxify);
result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
}
}
Expand Down Expand Up @@ -1609,7 +1645,7 @@ export function register(name, func, patternify = true, preserveTactus = false,

// toplevel functions get curried as well as patternified
// because pfunc uses spread args, we need to state the arity explicitly!
return curry(pfunc, null, arity);
return curry(pfunc, null, arity, proxify);
}

// Like register, but defaults to stepJoin
Expand Down
9 changes: 7 additions & 2 deletions packages/core/util.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,19 @@ export const constant = (a, b) => a;

export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);

export function curry(func, overload, arity = func.length) {
export function curry(func, overload, arity = func.length, final) {
const fn = function curried(...args) {
if (args.length >= arity) {
return func.apply(this, args);
} else {
const partial = function (...args2) {
let partial = function (...args2) {
return curried.apply(this, args.concat(args2));
};
if (final && arity - 1 === args.length) {
// for method composition
partial = final(partial);
}
// TODO - is overload used any more?
if (overload) {
overload(partial, args);
}
Expand Down
Loading