diff --git a/README.md b/README.md index 6bb8ef3..2c29a3e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ These are the functions under the `kudoJS.*` namespace * [`kudoJS.compose`](docs/helper-functions.md#kudojscompose) * [`kudoJS.constant`](docs/helper-functions.md#kudojsconstant) * [`kudoJS.fmap`](docs/helper-functions.md#kudojsfmap) +* [`kudoJS.assoc`](docs/helper-functions.md#kudojsassoc) * [`kudoJS.bimap`](docs/helper-functions.md#kudojsbimap) * [`kudoJS.chain`](docs/helper-functions.md#kudojschain) * [`kudoJS.caseOf`](docs/helper-functions.md#kudojscaseof) @@ -44,6 +45,7 @@ These are the functions under the `kudoJS.*` namespace * [`kudoJS.liftA4`](docs/helper-functions.md#kudojslifta4) * [`kudoJS.liftA5`](docs/helper-functions.md#kudojslifta5) * [`kudoJS.when`](docs/helper-functions.md#kudojswhen) +* [`kudoJS.pick`](docs/helper-functions.md#kudojspick) * [`kudoJS.prop`](docs/helper-functions.md#kudojsprop) * [`kudoJS.eitherToMaybe`](docs/helper-functions.md#kudojseithertomaybe) * [`kudoJS.maybeToEither`](docs/helper-functions.md#kudojsmaybeToEither) diff --git a/docs/helper-functions.md b/docs/helper-functions.md index 701c23e..50136c2 100644 --- a/docs/helper-functions.md +++ b/docs/helper-functions.md @@ -9,6 +9,7 @@ These are the functions under the `kudoJS.*` namespace - [`kudoJS.compose`](#kudojscompose) - [`kudoJS.constant`](#kudojsconstant) - [`kudoJS.fmap`](#kudojsfmap) + - [`kudoJS.assoc`](#kudojsassoc) - [`kudoJS.bimap`](#kudojsbimap) - [`kudoJS.chain`](#kudojschain) - [`kudoJS.caseOf`](#kudojscaseof) @@ -19,6 +20,7 @@ These are the functions under the `kudoJS.*` namespace - [`kudoJS.liftA5`](#kudojslifta5) - [`kudoJS.when`](#kudojswhen) - [`kudoJS.prop`](#kudojsprop) + - [`kudoJS.pick`](#kudojspick) - [`kudoJS.eitherToMaybe`](#kudojseithertomaybe) - [`kudoJS.maybeToEither`](#kudojsmaybetoeither) @@ -107,6 +109,22 @@ Takes a function and a functor, applies the function to each of the functor's va ---- + +### `kudoJS.assoc` + +Associates a value to the specified key in the object. This returns a clone of the original object + +`assoc(key: string, value: A, o: {[k:string]: A}): {[k:string]: A}` + + +| Param | Type | Description | +| --- | --- | --- | +| key | string | Key | +| value | function | Value to be assigned to key in object | +| o | Object | Object to which value needs to be assigned against the Key | + +---- + ### `kudoJS.bimap` Maps both sides of the disjunction @@ -259,6 +277,19 @@ Returns a Maybe Just if value exists for the given key else returns a Nothing ---- + +### `kudoJS.pick` +Returns a Maybe Just containing the object with just the keys if values exists for the given keys else returns a Nothing + +`pick( keys: Array, o: { [k: string]: A}): Maybe<{[k:string]: A}>` + +| Param | Type | Description | +| --- | --- | --- | +| keys | Array | Keys | +| o | Object | Key Value Object | + +---- + ### `kudoJS.eitherToMaybe` Converts an Either type to a Maybe Type diff --git a/src/adt/Maybe/index.ts b/src/adt/Maybe/index.ts index 527bbb6..404f36a 100644 --- a/src/adt/Maybe/index.ts +++ b/src/adt/Maybe/index.ts @@ -196,5 +196,15 @@ const _prop = ( if (!o) return Maybe.Nothing(); return key in o ? Maybe.fromNullable(o[key]) : Maybe.Nothing(); }; - export const prop = curry(_prop); + +function _pick(arr: Array, o: any) { + if (!o || arr.length <= 0) return Maybe.Nothing(); + const v = arr.reduce((acc: any, key) => { + if (o[key]) acc[key] = o[key]; + return acc; + }, {}); + + return Object.keys(v).length > 0 ? Maybe.Just(v) : Maybe.Nothing(); +} +export const pick = curry(_pick); diff --git a/src/adt/Maybe/maybe.test.ts b/src/adt/Maybe/maybe.test.ts index b85ebb7..b0df8d5 100644 --- a/src/adt/Maybe/maybe.test.ts +++ b/src/adt/Maybe/maybe.test.ts @@ -4,7 +4,7 @@ import compose from "../../function/compose"; import fmap from "../../function/fmap"; import id from "../../function/id"; import Either from "../Either"; -import Maybe, { eitherToMaybe, prop } from "."; +import Maybe, { eitherToMaybe, prop, pick } from "."; // const laws: any = require("laws"); // console.log(laws); @@ -216,5 +216,23 @@ test("Maybe", t => { "prop returns a Nothing if value does not exists" ); + t.equals( + Maybe.isNothing(pick(["d"], undefined)), + true, + "should return 'Nothing' if object is undefined" + ); + + t.equals( + Maybe.isNothing(pick(["d"], {})), + true, + "should return 'Nothing' if object is does not have key" + ); + + t.equals( + pick(["d"], { d: 1 }).getValue().d, + 1, + "should return 'Just(value)' if object has key" + ); + t.end(); }); diff --git a/src/function/assoc/assoc.test.ts b/src/function/assoc/assoc.test.ts new file mode 100644 index 0000000..d97081f --- /dev/null +++ b/src/function/assoc/assoc.test.ts @@ -0,0 +1,15 @@ +import * as test from "tape"; +import assoc from "./"; + +test("assoc", t => { + t.throws( + () => assoc("d", 1, undefined), + "should throw error if object is undefined" + ); + + t.equals(assoc("d", 1, {}).d, 1, "should set the value to a key in object"); + + const o = {}; + t.notEquals(assoc("d", 1, o), o, "should not mutate original object"); + t.end(); +}); diff --git a/src/function/assoc/index.ts b/src/function/assoc/index.ts new file mode 100644 index 0000000..61b3d80 --- /dev/null +++ b/src/function/assoc/index.ts @@ -0,0 +1,11 @@ +import curry from "../curry"; +import objectDeepClone from "../deepClone"; + +function assoc(key: string, val: A, obj: any): any { + if (!obj || typeof obj !== "object") + throw new Error("assoc: expects an object"); + const o = objectDeepClone(obj); + o[key] = val; + return o; +} +export default curry(assoc); diff --git a/src/function/deepClone/deepClone.test.ts b/src/function/deepClone/deepClone.test.ts new file mode 100644 index 0000000..48076a3 --- /dev/null +++ b/src/function/deepClone/deepClone.test.ts @@ -0,0 +1,16 @@ +import * as test from "tape"; +import deepClone from "./"; + +test("deep clone", t => { + t.equals(deepClone(1), 1, "should return value if type is not object"); + const o1 = { d: 1 }; + t.notEquals(deepClone(o1), o1, "should not mutate original object"); + const s = { p: 1 }; + const o2 = { d: s }; + t.notEquals( + deepClone(o2).d, + o2.d, + "returned value should not be deep equal to original value" + ); + t.end(); +}); diff --git a/src/function/deepClone/index.ts b/src/function/deepClone/index.ts new file mode 100644 index 0000000..655cb9d --- /dev/null +++ b/src/function/deepClone/index.ts @@ -0,0 +1,11 @@ +export default function objectDeepClone(o: any) { + if (o == null || typeof o !== "object") return o; + const clone: any = Array.isArray(o) ? [] : {}; + Object.keys(o).map(k => { + if (o.hasOwnProperty(k)) { + clone[k] = objectDeepClone(o[k]); + } + }); + + return clone; +} diff --git a/src/index.ts b/src/index.ts index 8db6e34..e2ad9b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ /* istanbul ignore file */ import Either, { maybeToEither } from "./adt/Either"; -import Maybe, { eitherToMaybe, prop } from "./adt/Maybe"; +import Maybe, { eitherToMaybe, pick, prop } from "./adt/Maybe"; import Pair from "./adt/Pair"; import Reader from "./adt/Reader"; import State from "./adt/State"; import Task from "./adt/Task"; +import assoc from "./function/assoc"; import bimap from "./function/bimap"; import caseOf from "./function/caseOf"; import chain from "./function/chain"; @@ -27,6 +28,7 @@ export default { Reader, State, Task, + assoc, bimap, caseOf, chain, @@ -42,5 +44,6 @@ export default { maybeToEither, ocurry, once, + pick, prop };