Skip to content

Instantly share code, notes, and snippets.

@Avaq

Avaq/placeholder.md

Last active Apr 15, 2019
Embed
What would you like to do?
Exploring an alternative placeholder implementation

I published a library which implements this idea; furry.

# From To Normal Alternative
1 a → b → c a → c f(_, b)(a) f(_, b)(a)
2 a → b → c b → a → c nope f(_)(b, a)
3 a → b → c → d b → d f(a, _, c)(b) f(a, _, c)(b)
4 a → b → c → d a → b → d f(_, _, c)(a, b) f(_, _, c)(a, b)
5 a → b → c → d a → c → d f(_, b)(a, c) f(_, b, _)(a, c)
6 a → b → c → d b → a → d nope f(_, _, c, _)(b, a)
7 a → b → c → d c → a → d nope f(_, b)(c, a)
8 a → b → c → d c → b → d nope f(a, _)(c, b)
9 a → b → c → d b → c → a → d nope f(_)(b, c, a)
10 a → b → c → d c → a → b → d nope f(_, _)(c, a, b)
11 a → b → c → d c → b → a → d nope nope
12 a → b → c → d b → a → c → d nope nope

The alternative has another advantage: You can supply the placeholders in curried fashion, something which is impossible with the normal semantics:

f(a, b, c) === f(_, _, c, _)(b, a) === f(_)(_)(c)(_)(b)(a) === f(_, _, c, _, b, a)

Notes

  • Any case where the last argument is the placeholder (like 2 and 8), is equivalent to applying flip to the function without providing the placeholder.
  • The alternative method almost provides the full power of rearg, except for cases like 11 and 12. These kinds of cases would become more common as the arity of the function increases.
  • The alternative method has exactly the same syntax for the majority of use-cases. Note that only 5 differs. These differences will become more abundant as the arity of the function increases.
  • The alternative method has more consistent behavior; there's no difference between calling your function f(_)(_)(_) or f(_, _, _).
console.clear();
const _ = {'@@functional/placeholder': true};
const is_ = x => x && x['@@functional/placeholder'] === true
//Rotates a function so that its first argument becomes its last.
const rotate = f => (...xs) => (xs.unshift(xs.pop()), f(...xs));
//Partially apply a function with an array of arguments.
const partial = (f, xs) => (...ys) => f(...[...xs, ...ys]);
//Curry a function based on its reported arity.
const curry = f => curryN(f.length, f);
//Curry a function using the given arity.
const curryN = (n, f) => (...xs) => {
const o = xs.reduce(
(o, x, i) => (
o.n === 0
? (o)
: is_(x)
? ({f: rotate(partial(o.f, o.xs)), n: o.n, xs: []})
: o.n === 1
? ({n: 0, f: o.f(...[...o.xs, x]), xs: []})
: i === xs.length - 1
? ({f: partial(o.f, [...o.xs, x]), n: o.n - 1, xs: []})
: ({f: o.f, n: o.n - 1, xs: [...o.xs, x]})
),
{f, n, xs: []}
);
return o.n === 0 ? o.f : curryN(o.n, o.f);
}
const test = curry((a, b, c, d) => a + b + c + d);
([
test('a', _, 'c', 'd', 'b'),
test('a', _, 'c', _, 'b', 'd'),
test(_, _, _, _)('a', 'b')('c', 'd'),
test(_, _, _, _, _, 'b', 'c', 'd', 'a'),
test(_)(_)(_)(_)('a')('b')('c')('d')
])
console.clear();
const _ = {'@@functional/placeholder': true};
const is_ = x => x && x['@@functional/placeholder'] === true
//Changes a curried function of arity n so that its first argument becomes its last
const rotate = (n, f) => {
if(n < 2) return f;
const next = xs => x => xs.length === n-1 ? xs.reduce((f, x) => f(x), f(x)) : next([...xs, x]);
return next([]);
}
//Changes a curried function of arity n so it may accept multiple arguments at once
const recurry = (n, f) => (...xs) => {
if(xs.length < 1) return recurry(n, f);
const r = xs.reduce(
({n, f}, x) => is_(x) ? {n, f: rotate(n, f)} : {n: n - 1, f: f(x)},
{n, f}
);
return r.n < 1 ? r.f : recurry(r.n, r.f);
}
const test = recurry(4, a => b => c => d => a + b + c + d);
([
test('a', _, 'c', 'd', 'b'),
test('a', _, 'c', _, 'b', 'd'),
test(_, _, _, _)('a', 'b')('c', 'd'),
test(_, _, _, _, _, 'b', 'c', 'd', 'a'),
test(_)(_)(_)(_)('a')('b')('c')('d')
])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.