├── .gitignore ├── yarn.lock ├── package.json ├── Readme.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "js-function-reflector@https://github.com/plygrynd-jynkyrd/js-function-reflector.git": 6 | version "1.3.0" 7 | resolved "https://github.com/plygrynd-jynkyrd/js-function-reflector.git#f3a1295909cf870bbb7bb136ca53796fc3e87c17" 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curry-experiment", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "stereobooster ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "js-function-reflector": "https://github.com/plygrynd-jynkyrd/js-function-reflector.git" 9 | }, 10 | "type": "module" 11 | } 12 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Currying reimagined 2 | 3 | [Code for blogpost](https://stereobooster.com/posts/currying-reimagined/). This is just an experiment for fun - don't it use in production. 4 | 5 | ```js 6 | const abc = ({ a, b, c }) => [a, b, c]; 7 | 8 | const curried = curry(abc); 9 | 10 | curried({ a: 1 })({ b: 2 })({ c: 3 }); 11 | // [1, 2, 3] 12 | curried({ a: 1, b: 2 })({ c: 3 }); 13 | // [1, 2, 3] 14 | curried({ a: 1, b: 2, c: 3 }); 15 | // [1, 2, 3] 16 | 17 | // In different order 18 | curried({ c: 3 })({ b: 2 })({ a: 1 }); 19 | // [1, 2, 3] 20 | curried({ c: 3, b: 2 })({ a: 1 }); 21 | // [1, 2, 3] 22 | 23 | // ... 24 | ``` 25 | 26 | ## See also 27 | 28 | https://docs-lodash.com/v4/curry/ 29 | 30 | ```js 31 | var abc = function (a, b, c) { 32 | return [a, b, c]; 33 | }; 34 | 35 | var curried = _.curry(abc); 36 | 37 | curried(1)(2)(3); 38 | // => [1, 2, 3] 39 | 40 | curried(1, 2)(3); 41 | // => [1, 2, 3] 42 | 43 | curried(1, 2, 3); 44 | // => [1, 2, 3] 45 | 46 | // Curried with placeholders. 47 | curried(1)(_, 3)(2); 48 | // => [1, 2, 3] 49 | ``` 50 | 51 | https://docs-lodash.com/v4/curry-right/ 52 | 53 | ```js 54 | var abc = function (a, b, c) { 55 | return [a, b, c]; 56 | }; 57 | 58 | var curried = _.curryRight(abc); 59 | 60 | curried(3)(2)(1); 61 | // => [1, 2, 3] 62 | 63 | curried(2, 3)(1); 64 | // => [1, 2, 3] 65 | 66 | curried(1, 2, 3); 67 | // => [1, 2, 3] 68 | 69 | // Curried with placeholders. 70 | curried(3)(1, _)(2); 71 | // => [1, 2, 3] 72 | ``` 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import reflector from "js-function-reflector"; 2 | 3 | const memoize = (fn) => { 4 | const store = new WeakMap(); 5 | return (arg) => { 6 | if (!store.has(arg)) { 7 | store.set(arg, fn(arg)); 8 | } 9 | return store.get(arg); 10 | }; 11 | }; 12 | 13 | const getMinMax = memoize((fn) => { 14 | try { 15 | const reflectedArgs = reflector(fn).args; 16 | const lastParameter = reflectedArgs[reflectedArgs.length - 1]; 17 | const isRestParameter = lastParameter && lastParameter.includes("..."); 18 | const isObjectArg = lastParameter && lastParameter.includes("}"); 19 | const min = reflectedArgs.filter( 20 | (x) => !Array.isArray(x) && (isObjectArg ? !x.includes("...") : true) 21 | ).length; 22 | const max = isRestParameter ? Infinity : reflectedArgs.length; 23 | return [min, max, isObjectArg]; 24 | } catch { 25 | // failed to do reflection 26 | return [fn.length, fn.length, undefined]; 27 | } 28 | }); 29 | 30 | const emptyObj = {}; 31 | export const curry = (fn, args1 = emptyObj) => { 32 | const [min, max, isObject] = getMinMax(fn); 33 | if (fn.length !== 1 || !isObject) { 34 | throw new TypeError(`Expect function with one object argument`); 35 | } 36 | const curriedFunction = (args2) => { 37 | if (Object.keys(args2).length === 0) { 38 | throw new TypeError(`Expects at least one argument`); 39 | } 40 | const fullArgs = { ...args1, ...args2 }; 41 | if (Object.keys(fullArgs).length > max) { 42 | throw new TypeError( 43 | `Expected ${min - Object.keys(args1).length}...${ 44 | max - Object.keys(args1).length 45 | } arguments, but got ${args2.length}` 46 | ); 47 | } 48 | if (Object.keys(fullArgs).length >= min) { 49 | return fn(fullArgs); 50 | } 51 | return curry(fn, fullArgs); 52 | }; 53 | curriedFunction.toString = () => 54 | `/* curried[${Object.keys(args1).length}/${min}...${max}] */ ${fn}`; 55 | return curriedFunction; 56 | }; 57 | --------------------------------------------------------------------------------