Last active
December 19, 2018 16:52
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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])._; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_(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