Skip to content

Instantly share code, notes, and snippets.

@gabejohnson
Last active December 19, 2018 16:52
Show Gist options
  • Save gabejohnson/59b3797cf887c3e4faab44da9774748f to your computer and use it in GitHub Desktop.
Save gabejohnson/59b3797cf887c3e4faab44da9774748f to your computer and use it in GitHub Desktop.
So you know how you've always wanted to write custom *fix operators? You haven't?!?! Well this isn't for you then
// fix.js
// _has :: String => Boolean
var _has = k => _[k] != null;
// last :: Array a => a?
var last = xs => xs[xs.length-1];
// copyArray :: Array a => Array a
var copyArray = xs => xs.slice(0);
// $ :: ((a => b), a) => b
var $ = (f, a) => f(a);
// applyArgs :: Array Any => (Any, String) => Any
var applyArgs = args => (result, c) =>
(c.match(/_/g) || [])
.slice(1)
.map(() => args.shift())
.reduce($, _[c](result));
// Program :: { args :: Array Any, calls :: Array String }
// runProgram :: Program => Any
var runProgram = ({args, calls}) => calls.reduce(applyArgs(args), args.shift());
// isKeyPrefix :: String => Boolean
var isKeyPrefix = p => Object.keys(_).some(k => k.startsWith(p));
// updateCalls :: (Array String, String) => Array String
var updateCalls =
(calls, key,
lastCall=last(calls),
potentialCall=lastCall + key) => {
if (!isKeyPrefix(potentialCall)) {
if (_has(lastCall)) {
calls.push('_' + key);
} else {
throw Error(`Undefined function '${lastCall}'`);
}
} else {
calls[calls.length-1] = potentialCall;
}
return calls;
};
var buildProxy = (args, calls, handler) =>
new Proxy(Object.assign(new Function, { args, calls }), handler);
var callHandler = {
apply(target, thisArg, newArgs, calls=copyArray(target.calls)) {
return buildProxy(
copyArray(target.args).concat(newArgs),
(calls[calls.length-1] = last(calls) + '_', calls),
accessHandler);
}
};
var accessHandler = {
get(target, key) {
const args = copyArray(target.args);
const calls = copyArray(target.calls);
return key === '_'
? runProgram({args, calls})
: buildProxy(args, updateCalls(calls, key), callHandler);
},
apply(target, thisArg, [[key]]) {
return buildProxy(copyArray(target.args),
updateCalls(copyArray(target.calls), key),
callHandler);
}
};
var _ = new Proxy(new Function, {
apply(target, thisArg, args) {
return buildProxy(args, ['_'], accessHandler);
},
get(target, key) {
return target[key] || buildProxy([], [key], callHandler);
}
});
// mixfix :: Array String => Function => Function
var mixfix = ([pattern]) => f => _[pattern] = f;
// _map_ :: (a => b) => Array a => Array b
mixfix `_map_` (f => xs => xs.map(f));
// _ap_ :: Array (a => b) => Array a => Array b
mixfix `_ap_` (fs => xs => fs.reduce((acc, f) => acc.concat(xs.map(f)), []));
_(x => y => x + y).map([1,2,3]).ap([4,5,6])._;
// if_then_else_ :: Boolean => (() => a) => (() => a) => a
mixfix `if_then_else_` (t => c => a => t ? c() : a());
// if_then_ :: Boolean => (() => a) => a | Boolean
mixfix `if_then_` (t => c => t && c());
// _if_then_else_ :: Boolean => (a => Boolean) => (a => b) => (a => b) => b
mixfix `_if_then_else_` (x => t => c => a => t(x) ? c(x) : a(x));
_.if (true)
.then (() => 'Yay!')
.else (() => 'Boo...')._;
// when_do_with_ :: (a => Boolean) => (a => b) => a => b?
mixfix `when_do_with_` (t => c => x => t(x) ? c(x) : void 0);
_.when(x => x > 0).do(x => x - 1).with(1)._; // 0
// _pipe_ :: a => (a => b) => b
mixfix `_pipe_` (x => f => f(x));
// inc :: Number => Number
var inc = x => x + 1;
_(0)
.pipe(inc)
.pipe(inc)
.pipe(inc)._; // 3
mixfix `_<$>_` (_._map_);
mixfix `_<*>_` (_._ap_);
_(x => y => x + y) `<$>` ([1,2,3]) `<*>` ([4,5,6])._;
mixfix `_<>_` (xs => ys => xs.concat(ys));
_([1,2,3])`<>`([4,5,6])._;
_(x => y => x + y)
/* => {
args: [x => y => x + y],
calls: ['_']
}*/
.map
/* => {
args: [x => y => x + y],
calls: ['_map']
}*/
([1,2])
/* => {
args: [x => y => x + y, [1,2]],
calls: ['_map_']
}*/
.ap // there's no function called _map_ap so
/* => {
args: [x => y => x + y, [1,2]],
calls: ['_map_', '_ap']
}*/
([3,4])
/* => {
args: [x => y => x + y, [1,2], [3,4]],
calls: ['_map_', '_ap_']
}*/
._
// => _['ap'] (_['map'] (x => y => x + y) ([1,2])) ([3,4])
// equivalent to:
// _.ap (_.map (x => y => x + y) ([1,2])) ([3,4])
// => [4, 5, 5, 6]
_.if
/* => {
args: [],
calls: ['if']
}*/
(true)
/* => {
args: [true],
calls: ['if_']
}*/
.then
/* => {
args: [true],
calls: ['if_then']
}*/
(() => "I see you!")
/* => {
args: [true, () => "I see you!"],
calls: ['if_then_']
}*/
._
// => _['if_then'] (true) (() => "I see you!")
// equivalent to:
// _.if_then (true) (() => "I see you!")
// => Just("I see you!")
_.if
/* => {
args: [],
calls: ['if']
}*/
(false)
/* => {
args: [false],
calls: ['if_']
}*/
.then
/* => {
args: [false],
calls: ['if_then']
}*/
(() => "Where are you?")
/* => {
args: [false, () => "Where are you?"],
calls: ['if_then_']
}*/
.else
/* => {
args: [false, () => "Where are you?"],
calls: ['if_then_else']
}*/
(() => "I see you!")
/* => {
args: [false, () => "Where are you?", () => "I see you!"],
calls: ['if_then_else_']
}*/
._
// => _['if_then_else'] (false) (() => "Where are you?") (() => "I see you!")
// equivalent to:
// _.if_then_else (false) (() => "Where are you?") (() => "I see you!")
// => "I see you!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment