diff --git a/__tests__/modules/assoc.test.js b/__tests__/modules/assoc.test.js index bdc08d7..e0b4d0f 100644 --- a/__tests__/modules/assoc.test.js +++ b/__tests__/modules/assoc.test.js @@ -5,7 +5,7 @@ describe('assoc', () => { const key = 'name' const value = {} const obj = { name: 'John' } - const result = assoc(value, key, obj) + const result = assoc(key, value, obj) expect(result).toEqual({ name: value @@ -16,7 +16,7 @@ describe('assoc', () => { const key = 'name' const value = {} const obj = { name: 'John' } - const result = assoc(value)(key)(obj) + const result = assoc(key)(value)(obj) expect(result).toEqual({ name: value diff --git a/__tests__/modules/assocPath.test.js b/__tests__/modules/assocPath.test.js new file mode 100644 index 0000000..998ca5a --- /dev/null +++ b/__tests__/modules/assocPath.test.js @@ -0,0 +1,29 @@ +import assocPath from '../../modules/assocPath' + +describe('Associate Path', () => { + test('associates a value to a nested path', () => { + const data = { + a: { + b: { + c: 1 + } + }, + d: 1 + } + + const path = ['a', 'b', 'c'] + const value = 2 + const expected = { + a: { + b: { + c: 2 + } + }, + d: 1 + } + + const actual = assocPath(path, value, data) + + expect(actual).toEqual(expected) + }) +}) diff --git a/__tests__/modules/over.test.js b/__tests__/modules/over.test.js new file mode 100644 index 0000000..de50cb1 --- /dev/null +++ b/__tests__/modules/over.test.js @@ -0,0 +1,27 @@ +import over from '../../modules/over' +import lensProp from '../../modules/lensProp' + +describe('Over', () => { + test('It sets the value at the given lens by applying the given function', () => { + const data = { + name: 'Tim', + age: 29 + } + + const nameLens = lensProp('name') + + const setName = name => { + expect(name).toBe('Tim') + return 'John' + } + + const expected = { + name: 'John', + age: 29 + } + + const actual = over(nameLens, setName, data) + + expect(actual).toEqual(expected) + }) +}) \ No newline at end of file diff --git a/__tests__/modules/set.test.js b/__tests__/modules/set.test.js new file mode 100644 index 0000000..8451f7d --- /dev/null +++ b/__tests__/modules/set.test.js @@ -0,0 +1,22 @@ +import set from '../../modules/set' +import lensProp from '../../modules/lensProp' + +describe('Set', () => { + test('It sets the given value on the given lens in the given data', () => { + const data = { + name: 'Tim', + age: 29 + } + + const nameLens = lensProp('name') + + const expected = { + name: 'John', + age: 29 + } + + const actual = set(nameLens, 'John', data) + + expect(actual).toEqual(expected) + }) +}) \ No newline at end of file diff --git a/__tests__/modules/view.test.js b/__tests__/modules/view.test.js new file mode 100644 index 0000000..17f3226 --- /dev/null +++ b/__tests__/modules/view.test.js @@ -0,0 +1,17 @@ +import view from '../../modules/view' +import lensProp from '../../modules/lensProp' + +describe('View', () => { + test('It returns the value at the lens', () => { + const data = { + name: 'Tim', + age: 29 + } + + const nameLens = lensProp('name') + const expected = 'Tim' + const actual = view(nameLens, data) + + expect(actual).toBe(expected) + }) +}) \ No newline at end of file diff --git a/modules/assoc.js b/modules/assoc.js index bc4a929..8f36a6f 100644 --- a/modules/assoc.js +++ b/modules/assoc.js @@ -8,12 +8,12 @@ import curry from './curry' * assoc(1, 'age', { age: 2, name: 'Tim' }) === { age: 1, name: 'Tim' } * * - * @param {*} value - The value to associate * @param {string} key - The key to associate + * @param {*} value - The value to associate * @param {Object} obj - The structure to associate with * @return {Object} */ -const assoc = (value, key, obj) => { +const assoc = (key, value, obj) => { let result = {} for (let k in obj) { diff --git a/modules/assocPath.js b/modules/assocPath.js new file mode 100644 index 0000000..27364bd --- /dev/null +++ b/modules/assocPath.js @@ -0,0 +1,35 @@ +import curry from './curry' +import clone from './clone' +import cloneType from './cloneType' +import assoc from './assoc' +import prop from './prop' + +/** + * Associates a value at a path + * + * @example + * + * assocPath(['profile', 'age'], 30, { profile: { age: 29 } } ) === { profile: { age: 30 } } + * + * + * @param {Array} key - The path to associate + * @param {*} value - The value to associate + * @param {Object} obj - The structure to associate with + * @return {Object} + */ +export const assocPath = (targetPath, value, data) => { + // Ensure we are not mutating anything + const path = targetPath.slice() + + // If we are at the end of our recursion + if (path.length < 2) { + return assoc(targetPath.shift(), value, data) + } + + const key = path.shift() + + // Associate the top key with the recursive result + return assoc(key, assocPath(path, value, prop(key, data)), data) +} + +export default curry(assocPath) \ No newline at end of file diff --git a/modules/cloneType.js b/modules/cloneType.js new file mode 100644 index 0000000..5ed2de3 --- /dev/null +++ b/modules/cloneType.js @@ -0,0 +1,7 @@ +export const cloneType = base => { + if (Array.isArray(base)) { + return [] + } + + return {} +} \ No newline at end of file diff --git a/modules/lens.js b/modules/lens.js new file mode 100644 index 0000000..0277c12 --- /dev/null +++ b/modules/lens.js @@ -0,0 +1,14 @@ +import curry from './curry' + +/** + * A way to get and set values in a data structure + * + * @param {function(any) => any} get + * @param {function(any, a) => a} set + */ +export const lens = (get, set) => ({ + get, + set +}) + +export default curry(lens) \ No newline at end of file diff --git a/modules/lensPath.js b/modules/lensPath.js new file mode 100644 index 0000000..cf9fde1 --- /dev/null +++ b/modules/lensPath.js @@ -0,0 +1,14 @@ +import curry from './curry' +import path from './path' +import lens from './lens' +import assocPath from './assocPath' + +/** + * A way to create a lens for a given path + * + * @param {Array} keys - The path to the data we care about + * @returns {Lens} - A lens to the Path + */ +export const lensPath = keys => lens(path(keys), assocPath(keys)) + +export default curry(lensPath) \ No newline at end of file diff --git a/modules/lensProp.js b/modules/lensProp.js new file mode 100644 index 0000000..550a256 --- /dev/null +++ b/modules/lensProp.js @@ -0,0 +1,12 @@ +import curry from './curry' +import lensPath from './lensPath' + +/** + * Creates a lens at a given path + * + * @param {string|number} key - The key to associate this lens with + * @return {Lens} + */ +export const lensProp = key => lensPath([key]) + +export default curry(lensProp) \ No newline at end of file diff --git a/modules/over.js b/modules/over.js new file mode 100644 index 0000000..90e9a32 --- /dev/null +++ b/modules/over.js @@ -0,0 +1,15 @@ +import curry from './curry' + +/** + * A way to set a value on a lens via a function + * The function gets passed the value at the key along with the + * data structure + * + * @param {Lens} lens - The lens to the value to set + * @param {function(*, *) => *} fn - A function to immutably create a new value + * @param {*} data - The data to look in + * @return {*} + */ +export const over = (lens, fn, data) => lens.set(fn(lens.get(data)), data) + +export default curry(over) \ No newline at end of file diff --git a/modules/set.js b/modules/set.js new file mode 100644 index 0000000..ba21efe --- /dev/null +++ b/modules/set.js @@ -0,0 +1,13 @@ +import curry from './curry' + +/** + * Sets the given value at the given lens + * + * @param {Lens} lens - The lens to the value + * @param {*} value - The value to set at the lens + * @param {*} data - The data structure to look in + * @return {*} - The data with the lens set + */ +export const set = (lens, value, data) => lens.set(value, data) + +export default curry(set) \ No newline at end of file diff --git a/modules/view.js b/modules/view.js new file mode 100644 index 0000000..8e22463 --- /dev/null +++ b/modules/view.js @@ -0,0 +1,12 @@ +import curry from './curry' + +/** + * A way to actually view the value + * + * @param {Lens} lens - The lens to the data + * @param {*} data - The data structure to look in + * @return {*} + */ +export const view = (lens, data) => lens.get(data) + +export default curry(view) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2f9488f..5ae7717 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1511,7 +1511,7 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "2.0.0" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -2866,7 +2866,7 @@ import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3765,10 +3765,6 @@ lodash._basecopy@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -3776,14 +3772,10 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*, lodash._bindcallback@^3.0.0: +lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - lodash._createassigner@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" @@ -3792,17 +3784,11 @@ lodash._createassigner@^3.0.0: lodash._isiterateecall "^3.0.0" lodash.restparam "^3.0.0" -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" -lodash._getnative@*, lodash._getnative@^3.0.0: +lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" @@ -3862,7 +3848,7 @@ lodash.merge@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" -lodash.restparam@*, lodash.restparam@^3.0.0: +lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" @@ -5138,7 +5124,7 @@ readable-stream@~2.0.5: string_decoder "~0.10.x" util-deprecate "~1.0.1" -readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0: +readdir-scoped-modules@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" dependencies: @@ -6217,7 +6203,7 @@ uuid@^3.0.0, uuid@^3.1.0, uuid@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -validate-npm-package-license@*, validate-npm-package-license@^3.0.1: +validate-npm-package-license@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" dependencies: