diff --git a/.babelrc b/.babelrc
index c2c6469f9..f04540303 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,6 +1,7 @@
{
"plugins": [
"transform-es2015-arrow-functions",
+ "transform-es2015-computed-properties",
"transform-es2015-shorthand-properties",
"transform-es2015-template-literals"
]
diff --git a/README.md b/README.md
index e911cab64..ea06adf92 100644
--- a/README.md
+++ b/README.md
@@ -153,6 +153,18 @@ substitution : (a -> b -> c) -> (a -> b) -> a -> c
While it a complicated little bugger, it can come in very handy from time to time. In it's first two arguments it takes functions. The first must be binary and the second unary. That will return you a function that is ready to take some value. Once supplied the fun starts, it will pass the `a` to the first argument of both functions, and the result of the second function to the second parameter of the first function. Finally after all that juggling, it will return the result of that first function. When used with partial application on that first parameter, a whole new world of combinatory madness is presented!
### Helper Functions
+#### assign
+```haskell
+assign : Object -> Object -> Object
+```
+When working with `Object`s, a common operation is to combine (2) of them. This can be accomplished in `crocks` by reaching for `assign`. Unlike the `Object.assign` that ships with JavaScript, this `assign` will combine your `Object`s into a new shallow copy of their merger. `assign` only takes two arguments and will overwrite keys present in the second argument with values from the first. As with most of the `crocks` `Object` based functions, `assign` will omit any key-value pairs that are `undefined`. Check out a related function named [`defaultProps`](#defaultprops) that will only assign values that are `undefined` in the second argument.
+
+#### assoc
+```haskell
+assoc : String -> a -> Object -> Object
+```
+There may come a time when you want to add a key-value pair to an `Object` and want control over how the key and value are applied. That is where `assoc` can come to your aid. Just provide a `String` key and a value of any type to be associated to the key. Finally pass it any `Object` and you will get back a shallow copy with your key-value pair merged in. This will overwrite any exiting keys with new value specified. Used with [`flip`](#flip), you can do some interesting things with this function, give it a play! If you just want to create an `Object` and not concatenate it to another `Object`, [`objOf`](#objof) may be the function for you.
+
#### branch
```haskell
branch : a -> Pair a a
@@ -201,6 +213,18 @@ curryN : Number -> ((a, b, ...) -> z) -> a -> b -> ... -> z
```
When dealing with variable argument functions, there are times when you may want to curry a specific number of arguments. Just pass this function the number of arguments you want to curry as well as the function, and it will not call the wrapped function until all arguments have been passed. This function will ONLY pass the number of arguments that you specified, any remaining arguments are discarded. This is great for limiting the arity of a given function when additional parameters are defaulted or not needed. This function will not auto curry functions returning functions like [`curry`](#curry) does, use with [`curry`](#curry) if you need to do some fancy setup for your wrapped function's API.
+#### defaultProps
+```haskell
+defaultProps : Object -> Object -> Object
+```
+Picture this, you have an `Object` and you want to make sure that some properties are set with a given default value. When the need for this type of operation presents itself, `defaultProps` can come to your aid. Just pass it an `Object` that defines your defaults and then the `Object` your want to default those props on. If a key that is present on the defaults `Object` is not defined on your data, then the default value will be used. Otherwise, the value from your data will be used instead. You could just apply [`flip`](#flip) to the [`assign`](#assign) function and get the same result, but having a function named `defaultProps` may be easier to read in code. As with most `Object` related functions in `crocks`, `defaultProps` will return you a shallow copy of the result and not include any `undefined` values in either `Object`.
+
+#### defaultTo
+```haskell
+defaultTo : a -> b -> a
+```
+With things like `null`, `undefined` and `NaN` showing up all over the place, it can be hard to keep your expected types inline without resorting to nesting in a `Maybe` with functions like [`safe`](#safe). If you want to specifically guard for `null`, `undefined` and `NaN` and get things defaulted into the expected type, then `defaultTo` should work for you. Just pass it what you would like your default value to be and then the value you want guarded, and you will get back either the default or the passed value, depending on if the passed value is `null`, `undefined` or `NaN`. While this *is* JavaScript and you can return anything, it is suggested to stick to the signature and only let `a`s through. As a `b` can be an `a` as well.
+
#### fanout
```haskell
fanout : (a -> b) -> (a -> c) -> (a -> Pair b c)
@@ -209,6 +233,12 @@ fanout : Monad m => Star a (m b) -> Star a (m c) -> Star a (m (Pair b c))
```
There are may times that you need to keep some running or persistent state while performing a given computation. A common way to do this is to take the input to the computation and branch it into a `Pair` and perform different operations on each version of the input. This is such a common pattern that it warrants the `fanout` function to take care of the initial split and mapping. Just provide a pair of either simple functions or a pair of one of the computation types (`Arrow` or `Star`). You will get back something of the same type that is configured to split it's input into a pair and than apply the first Function/ADT to the first portion of the underlying `Pair` and the second on the second.
+#### fromPairs
+```haskell
+fromPairs : [ (Pair String a) ] | List (Pair String a) -> Object
+```
+As an inverse to [`toPairs`](#topairs), `fromPairs` takes either an `Array` or `List` of key-value `Pair`s and constructs an `Object` from it. The `Pair` must contain a `String` in the `fst` and any type of value in the `snd`. The `fst` will become the key for the value in the `snd`. All primitive values are copied into the new `Object`, while non-primitives are references to the original. If you provide an `undefined` values for the second, that `Pair` will not be represented in the resulting `Object`. Also, when if multiple keys share the same name, that last value will be moved over.
+
#### liftA2
#### liftA3
```haskell
@@ -233,12 +263,30 @@ mreduceMap : Monoid m => m -> (b -> a) -> ([ b ] | List b) -> a
```
There comes a time where the values you have in a `List` or an `Array` are not in the type that is needed for the [`Monoid`](#monoids) you want to combine with. These two functions can be used to `map` some transforming function from a given type into the type needed for the [`Monoid`](#monoids). In essence, this function will run each value through the function before it lifts the value into the [`Monoid`](#monoids), before `concat` is applied. The difference between the two is that `mconcatMap` returns the result inside the [`Monoid`](#monoids) used to combine them. Where `mreduceMap` returns the bare value itself.
+#### objOf
+```haskell
+objOf : String -> a -> Object
+```
+If you ever find yourself in a situation where you have a key and a value and just want to combine the two into an `Object`, then it sounds like `objOf` is the function for you. Just pass it a `String` for the key and any type of value, and you'll get back an `Object` that is composed of those two. If you find yourself constantly concatenating the result of this function into another `Object`, you may want to use [`assoc`](#assoc) instead.
+
+#### omit
+```haskell
+omit : ([ String ] | List String) -> Object -> Object
+```
+Sometimes you just want to strip `Object`s of unwanted properties by key. Using `omit` will help you get that done. Just pass it a `Foldable` structure with a series of `String`s as keys and then pass it an `Object` and you will get back not only a shallow copy, but also an `Object` free of any of those pesky `undefined` values. You can think of `omit` as a way to black-list or reject `Object` properties based on key names. This function ignores inherited properties and should only be used with POJOs. If you want to filter or white-list properties rather than reject them, take a look at [`pick`](#pick).
+
#### once
```haskell
once : ((*) -> a) -> ((*) -> a)
```
There are times in Javascript development where you only want to call a function once and memo-ize the first result for every subsequent call to that function. Just pass the function you want guarded to `once` and you will get back a function with the expected guarantees.
+#### pick
+```haskell
+pick : ([ String ] | List String) -> Object -> Object
+```
+When dealing with `Object`s, sometimes it is necessary to only let some of the key-value pairs on an object through. Think of `pick` as a sort of white-list or filter for `Object` properties. Pass it a `Foldable` structure of `String`s that are the keys you would like to pick off of your `Object`. This will give you back a shallow copy of the key-value pairs you specified. This function will ignore inherited properties and should only be used with POJOs. Any `undefined` values will not be copied over, although `null` values are allowed. For black-listing properties, have a look at [`omit`](#omit).
+
#### pipe
```haskell
pipe : ((a -> b), (b -> c), ..., (y -> z)) -> a -> z
@@ -265,13 +313,13 @@ const promPipe =
#### prop
```haskell
-prop : (String | Number) -> a -> Maybe b
+prop : (String | Number) -> (Object | Array) -> Maybe a
```
If you want some safety around pulling a value out of an Object or Array with a single key or index, you can always reach for `prop`. Well, as long as you are working with non-nested data that is. Just tell `prop` either the key or index you are interested in, and you will get back a function that will take anything and return a `Just` with the wrapped value if the key/index exists. If the key/index does not exist however, you will get back a `Nothing`.
#### propPath
```haskell
-propPath : [ String | Number ] -> a -> Maybe b
+propPath : [ String | Number ] -> (Object | Array) -> Maybe a
```
While [`prop`](#prop) is good for simple, single-level structures, there may come a time when you have to work with nested POJOs or Arrays. When you run into this situation, just pull in `propPath` and pass it a left-to-right traversal path of keys, indices or a combination of both (gross...but possible). This will kick you back a function that behaves just like [`prop`](#prop). You pass it some data, and it will attempt to resolve your provided path. If the path is valid, it will return the value residing there (`null` included!) in a `Just`. But if at any point that path "breaks" it will give you back a `Nothing`.
@@ -293,6 +341,12 @@ tap : (a -> b) -> a -> a
```
It is hard knowing what is going on inside of some of these ADTs or your wonderful function compositions. Debugging can get messy when you need to insert a side-effect into your flow for introspection purposes. With `tap`, you can intervene in your otherwise pristine flow and make sure that the original value is passed along to the next step of your flow. This function does not guarantee immutability for reference types (`Objects`, `Arrays`, etc), you will need to exercise some discipline here to not mutate.
+#### toPairs
+```haskell
+toPairs : Object -> List (Pair String a)
+```
+When dealing with `Object`s, sometimes it makes more sense to work in a `Foldable` structure like a `List` of key-value `Pair`s. `toPairs` provides a means to take an object and give you back a `List` of `Pairs` that have a `String` that represents the key in the `fst` and the value for that key in the `snd`. The primitive values are copied, while non-primitive values are references. Like most of the `Object` functions in `crocks`, any keys with `undefined` values will be omitted from the result. `crocks` provides an inverse to this function named [`fromPairs`](#frompairs).
+
#### tryCatch
```haskell
tryCatch : (a -> b) -> a -> Result e b
@@ -356,8 +410,8 @@ All functions in this group have a signature of `* -> Boolean` and are used with
* `isInteger : a -> Boolean`: Integer
* `isMonad : a -> Boolean`: an ADT that provides `map`, `ap`, `chain` and `of` functions
* `isMonoid : a -> Boolean`: an ADT that provides `concat` and `empty` functions
-* `isNil : a -> Boolean`: undefined or null
-* `isNumber : a -> Boolean`: Number that is not a NaN value, Infinity included
+* `isNil : a -> Boolean`: `undefined` or `null` or `NaN`
+* `isNumber : a -> Boolean`: `Number` that is not a `NaN` value, `Infinity` included
* `isObject : a -> Boolean`: Plain Old Javascript Object (POJO)
* `isPromise : a -> Boolean`: An object implementing `then` and `catch`
* `isSameType : a -> b -> Boolean`: Constructor matches a values type, or two values types match
@@ -411,7 +465,7 @@ These functions provide a very clean way to build out very simple functions and
| `either` | `(a -> c) -> (b -> c) -> m a b -> c` |
| `evalWith` | `a -> m -> b` |
| `execWith` | `a -> m -> b` |
-| `filter` | `((a -> Boolean) | Pred a) -> m a -> m a` |
+| `filter` | ((a -> Boolean) | Pred a) -> m a -> m a
|
| `first` | `m (a -> b) -> m (Pair a c -> Pair b c)` |
| `fold` | `Semigroup s => m s -> s` |
| `fst` | `m a b -> a` |
@@ -423,7 +477,7 @@ These functions provide a very clean way to build out very simple functions and
| `promap` | `(c -> a) -> (b -> d) -> m a b -> m c d` |
| `read` | `m a b -> a` |
| `reduce` | `(b -> a -> b) -> b -> m a -> b` |
-| `reject` | `((a -> Boolean) | Pred a) -> m a -> m a` |
+| `reject` | ((a -> Boolean) | Pred a) -> m a -> m a
|
| `run` | `m a -> b` |
| `runWith` | `a -> m -> b` |
| `second` | `m (a -> b) -> m (Pair c a -> Pair c b)` |
@@ -455,7 +509,7 @@ These functions provide a very clean way to build out very simple functions and
| `fst` | `Pair` |
| `head` | `Array`, `List`, `String` |
| `log` | `Writer` |
-| `map` | `Async`, `Array`, `Arrow`, `Const`, `Either`, `Function`, `Identity`, `IO`, `List`, `Maybe`, `Pair`, `Reader`, `Result`, `Star`, `State`, `Unit`, `Writer` |
+| `map` | `Async`, `Array`, `Arrow`, `Const`, `Either`, `Function`, `Identity`, `IO`, `List`, `Maybe`, `Object`, `Pair`, `Reader`, `Result`, `Star`, `State`, `Unit`, `Writer` |
| `merge` | `Pair` |
| `option` | `Maybe` |
| `promap` | `Arrow`, `Star` |
diff --git a/crocks.js b/crocks.js
index 04bb1c9a6..599e2ee5d 100644
--- a/crocks.js
+++ b/crocks.js
@@ -31,19 +31,27 @@ const crocks = {
}
const helpers = {
+ assign: require('./helpers/assign'),
+ assoc: require('./helpers/assoc'),
branch: require('./helpers/branch'),
compose: require('./helpers/compose'),
composeP: require('./helpers/composeP'),
curry: require('./helpers/curry'),
curryN: require('./helpers/curryN'),
+ defaultProps: require('./helpers/defaultProps'),
+ defaultTo: require('./helpers/defaultTo'),
fanout: require('./helpers/fanout'),
+ fromPairs: require('./helpers/fromPairs'),
liftA2: require('./helpers/liftA2'),
liftA3: require('./helpers/liftA3'),
mconcat: require('./helpers/mconcat'),
mconcatMap: require('./helpers/mconcatMap'),
mreduce: require('./helpers/mreduce'),
mreduceMap: require('./helpers/mreduceMap'),
+ objOf: require('./helpers/objOf'),
+ omit: require('./helpers/omit'),
once: require('./helpers/once'),
+ pick: require('./helpers/pick'),
pipe: require('./helpers/pipe'),
pipeP: require('./helpers/pipeP'),
prop: require('./helpers/prop'),
@@ -51,6 +59,7 @@ const helpers = {
safe: require('./helpers/safe'),
safeLift: require('./helpers/safeLift'),
tap: require('./helpers/tap'),
+ toPairs: require('./helpers/toPairs'),
tryCatch: require('./helpers/tryCatch')
}
diff --git a/crocks.spec.js b/crocks.spec.js
index df0c60010..a18997fc8 100644
--- a/crocks.spec.js
+++ b/crocks.spec.js
@@ -10,19 +10,27 @@ const identity = require('./combinators/identity')
const reverseApply = require('./combinators/reverseApply')
const substitution = require('./combinators/substitution')
+const assign = require('./helpers/assign')
+const assoc = require('./helpers/assoc')
const branch = require('./helpers/branch')
const compose = require('./helpers/compose')
const composeP = require('./helpers/composeP')
const curry = require('./helpers/curry')
const curryN = require('./helpers/curryN')
+const defaultProps = require('./helpers/defaultProps')
+const defaultTo = require('./helpers/defaultTo')
const fanout = require('./helpers/fanout')
+const fromPairs = require('./helpers/fromPairs')
const liftA2 = require('./helpers/liftA2')
const liftA3 = require('./helpers/liftA3')
const mconcat = require('./helpers/mconcat')
const mconcatMap = require('./helpers/mconcatMap')
const mreduce = require('./helpers/mreduce')
const mreduceMap = require('./helpers/mreduceMap')
+const objOf = require('./helpers/objOf')
+const omit = require('./helpers/omit')
const once = require('./helpers/once')
+const pick = require('./helpers/pick')
const pipe = require('./helpers/pipe')
const pipeP = require('./helpers/pipeP')
const prop = require('./helpers/prop')
@@ -30,6 +38,7 @@ const propPath = require('./helpers/propPath')
const safe = require('./helpers/safe')
const safeLift = require('./helpers/safeLift')
const tap = require('./helpers/tap')
+const toPairs = require('./helpers/toPairs')
const tryCatch = require('./helpers/tryCatch')
const alt = require('./pointfree/alt')
@@ -149,19 +158,27 @@ test('entry', t => {
t.equal(crocks.reverseApply, reverseApply, 'provides the T combinator (reverseApply)')
t.equal(crocks.substitution, substitution, 'provides the S combinator (substitution)')
+ t.equal(crocks.assign, assign, 'provides the assign function')
+ t.equal(crocks.assoc, assoc, 'provides the assoc function')
t.equal(crocks.branch, branch, 'provides the branch function')
t.equal(crocks.compose, compose, 'provides the compose function')
t.equal(crocks.composeP, composeP, 'provides the composeP function')
t.equal(crocks.curry, curry, 'provides the curry function')
t.equal(crocks.curryN, curryN, 'provides the curryN function')
+ t.equal(crocks.defaultProps, defaultProps, 'provides the defaultProps function')
+ t.equal(crocks.defaultTo, defaultTo, 'provides the defaultTo function')
t.equal(crocks.fanout, fanout, 'provides the fanout function')
+ t.equal(crocks.fromPairs, fromPairs, 'provides the fromPairs function')
t.equal(crocks.liftA2, liftA2, 'provides the liftA2 function')
t.equal(crocks.liftA3, liftA3, 'provides the liftA3 function')
t.equal(crocks.mconcat, mconcat, 'provides the mconcat function')
t.equal(crocks.mconcatMap, mconcatMap, 'provides the mconcatMap function')
t.equal(crocks.mreduce, mreduce, 'provides the mreduce function')
t.equal(crocks.mreduceMap, mreduceMap, 'provides the mreduceMap function')
+ t.equal(crocks.objOf, objOf, 'provides the objOf function')
+ t.equal(crocks.omit, omit, 'provides the omit function')
t.equal(crocks.once, once, 'provides the once function')
+ t.equal(crocks.pick, pick, 'provides the pick function')
t.equal(crocks.pipe, pipe, 'provides the pipe function')
t.equal(crocks.pipeP, pipeP, 'provides the pipeP function')
t.equal(crocks.prop, prop, 'provides the prop function')
@@ -169,6 +186,7 @@ test('entry', t => {
t.equal(crocks.safe, safe, 'provides the safe function')
t.equal(crocks.safeLift, safeLift, 'provides the safeLift function')
t.equal(crocks.tap, tap, 'provides the tap function')
+ t.equal(crocks.toPairs, toPairs, 'provides the tap function')
t.equal(crocks.tryCatch, tryCatch, 'provides the tryCatch function')
t.equal(crocks.alt, alt, 'provides the alt point-free function')
diff --git a/helpers/assign.js b/helpers/assign.js
new file mode 100644
index 000000000..948a61fbe
--- /dev/null
+++ b/helpers/assign.js
@@ -0,0 +1,18 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isObject = require('../predicates/isObject')
+
+const object = require('../internal/object')
+
+// assign : Object -> Object -> Object
+function assign(x, m) {
+ if(!(isObject(x) && isObject(m))) {
+ throw new TypeError('assign: Objects required for both arguments')
+ }
+
+ return object.assign(x, m)
+}
+
+module.exports = curry(assign)
diff --git a/helpers/assign.spec.js b/helpers/assign.spec.js
new file mode 100644
index 000000000..8225c613a
--- /dev/null
+++ b/helpers/assign.spec.js
@@ -0,0 +1,60 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const assign = require('./assign')
+
+test('assign', t => {
+ const fn = bindFunc(assign)
+
+ const err = /assign: Objects required for both arguments/
+ t.throws(fn(undefined, {}), err, 'throws when first arg is undefined')
+ t.throws(fn(null, {}), err, 'throws when first arg is null')
+ t.throws(fn(0, {}), err, 'throws when first arg is a falsey number')
+ t.throws(fn(1, {}), err, 'throws when first arg is a truthy number')
+ t.throws(fn('', {}), err, 'throws when first arg is a falsey string')
+ t.throws(fn('string', {}), err, 'throws when first arg is a truthy string')
+ t.throws(fn(false, {}), err, 'throws when first arg is false')
+ t.throws(fn(true, {}), err, 'throws when first arg is true')
+ t.throws(fn(noop, {}), err, 'throws when first arg is a function')
+ t.throws(fn([], {}), err, 'throws when first arg is an array')
+
+ t.throws(fn({}, undefined), err, 'throws when second arg is undefined')
+ t.throws(fn({}, null), err, 'throws when second arg is null')
+ t.throws(fn({}, 0), err, 'throws when second arg is a falsey number')
+ t.throws(fn({}, 1), err, 'throws when second arg is a truthy number')
+ t.throws(fn({}, ''), err, 'throws when second arg is a falsey string')
+ t.throws(fn({}, 'string'), err, 'throws when second arg is a truthy string')
+ t.throws(fn({}, false), err, 'throws when second arg is false')
+ t.throws(fn({}, true), err, 'throws when second arg is true')
+ t.throws(fn({}, noop), err, 'throws when second arg is a function')
+ t.throws(fn({}, []), err, 'throws when second arg is an array')
+
+ const first = {
+ a: 'first', c: 'first', d: undefined, e: null, f: undefined
+ }
+
+ const second = {
+ a: 'second', b: 'second', e: undefined, f: null, g: undefined
+ }
+
+ const result = assign(first, second)
+ const keyExists =
+ key => Object.keys(result).indexOf(key) !== -1
+
+ const getValue =
+ key => result[key]
+
+ t.notOk(keyExists('d'), 'undefined values in first arg not included')
+ t.notOk(keyExists('g'), 'undefined values in second arg not included')
+
+ t.equals(getValue('a'), 'first', 'first arg value overwrites values from second arg')
+ t.equals(getValue('b'), 'second', 'second arg value included when not on first arg')
+ t.equals(getValue('c'), 'first', 'first arg value included when not on second arg')
+ t.equals(getValue('e'), null, 'second arg used when value undefined in first arg')
+ t.equals(getValue('f'), null, 'first arg used when value undefined in second arg')
+
+ t.end()
+})
diff --git a/helpers/assoc.js b/helpers/assoc.js
new file mode 100644
index 000000000..866a88bd3
--- /dev/null
+++ b/helpers/assoc.js
@@ -0,0 +1,21 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isObject = require('../predicates/isObject')
+const isString = require('../predicates/isString')
+
+const object = require('../internal/object')
+
+// assoc : String -> a -> Object -> Object
+function assoc(key, val, obj) {
+ if(!isString(key)) {
+ throw new TypeError('assoc: String required for first argument')
+ }
+ else if(!isObject(obj)) {
+ throw new TypeError('assoc: Object required for third argument')
+ }
+ return object.assign({ [key]: val }, obj)
+}
+
+module.exports = curry(assoc)
diff --git a/helpers/assoc.spec.js b/helpers/assoc.spec.js
new file mode 100644
index 000000000..52b239865
--- /dev/null
+++ b/helpers/assoc.spec.js
@@ -0,0 +1,42 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const assoc = require('./assoc')
+
+test('assoc', t => {
+ const fn = bindFunc(assoc)
+
+ const noKey = /assoc: String required for first argument/
+ t.throws(fn(undefined, 1, {}), noKey, 'throws when first arg is undefined')
+ t.throws(fn(null, 1, {}), noKey, 'throws when first arg is null')
+ t.throws(fn(0, 1, {}), noKey, 'throws when first arg is a falsey number')
+ t.throws(fn(1, 1, {}), noKey, 'throws when first arg is a truthy number')
+ t.throws(fn(false, 1, {}), noKey, 'throws when first arg is false')
+ t.throws(fn(true, 1, {}), noKey, 'throws when first arg is true')
+ t.throws(fn(noop, 1, {}), noKey, 'throws when first arg is a function')
+ t.throws(fn([], 1, {}), noKey, 'throws when first arg is an array')
+ t.throws(fn({}, 1, {}), noKey, 'throws when first arg is an object')
+
+ const noObj = /assoc: Object required for third argument/
+ t.throws(fn('key', 1, undefined), noObj, 'throws when third arg is undefined')
+ t.throws(fn('key', 1, null), noObj, 'throws when third arg is null')
+ t.throws(fn('key', 1, 0), noObj, 'throws when third arg is a falsey number')
+ t.throws(fn('key', 1, 1), noObj, 'throws when third arg is a truthy number')
+ t.throws(fn('key', 1, ''), noObj, 'throws when third arg is a falsey string')
+ t.throws(fn('key', 1, 'string'), noObj, 'throws when third arg is a truthy string')
+ t.throws(fn('key', 1, false), noObj, 'throws when third arg is false')
+ t.throws(fn('key', 1, true), noObj, 'throws when third arg is true')
+ t.throws(fn('key', 1, noop), noObj, 'throws when third arg is a function')
+ t.throws(fn('key', 1, []), noObj, 'throws when third arg is an array')
+
+ const data = { a: 45, b: 23 }
+
+ t.same(assoc('c', 10, data), { a: 45, b: 23, c: 10 }, 'adds a new key when it does not exist')
+ t.same(assoc('b', 10, data), { a: 45, b: 10 }, 'overrides an existing key when it exists')
+ t.same(data, { a: 45, b: 23 }, 'does not modify exsiting object (shallow)')
+
+ t.end()
+})
diff --git a/helpers/defaultProps.js b/helpers/defaultProps.js
new file mode 100644
index 000000000..c1dd22cb7
--- /dev/null
+++ b/helpers/defaultProps.js
@@ -0,0 +1,18 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isObject = require('../predicates/isObject')
+
+const object = require('../internal/object')
+
+// defaultProps : Object -> Object -> Object
+function defaultProps(x, m) {
+ if(!isObject(x) || !isObject(m)) {
+ throw new TypeError('defaultProps: Objects required for both arguments')
+ }
+
+ return object.assign(m, x)
+}
+
+module.exports = curry(defaultProps)
diff --git a/helpers/defaultProps.spec.js b/helpers/defaultProps.spec.js
new file mode 100644
index 000000000..b31b4ab68
--- /dev/null
+++ b/helpers/defaultProps.spec.js
@@ -0,0 +1,60 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const defaultProps = require('./defaultProps')
+
+test('defaultProps', t => {
+ const fn = bindFunc(defaultProps)
+
+ const err = /defaultProps: Objects required for both arguments/
+ t.throws(fn(undefined, {}), err, 'throws when first arg is undefined')
+ t.throws(fn(null, {}), err, 'throws when first arg is null')
+ t.throws(fn(0, {}), err, 'throws when first arg is a falsey number')
+ t.throws(fn(1, {}), err, 'throws when first arg is a truthy number')
+ t.throws(fn('', {}), err, 'throws when first arg is a falsey string')
+ t.throws(fn('string', {}), err, 'throws when first arg is a truthy string')
+ t.throws(fn(false, {}), err, 'throws when first arg is false')
+ t.throws(fn(true, {}), err, 'throws when first arg is true')
+ t.throws(fn(noop, {}), err, 'throws when first arg is a function')
+ t.throws(fn([], {}), err, 'throws when first arg is an array')
+
+ t.throws(fn({}, undefined), err, 'throws when second arg is undefined')
+ t.throws(fn({}, null), err, 'throws when second arg is null')
+ t.throws(fn({}, 0), err, 'throws when second arg is a falsey number')
+ t.throws(fn({}, 1), err, 'throws when second arg is a truthy number')
+ t.throws(fn({}, ''), err, 'throws when second arg is a falsey string')
+ t.throws(fn({}, 'string'), err, 'throws when second arg is a truthy string')
+ t.throws(fn({}, false), err, 'throws when second arg is false')
+ t.throws(fn({}, true), err, 'throws when second arg is true')
+ t.throws(fn({}, noop), err, 'throws when second arg is a function')
+ t.throws(fn({}, []), err, 'throws when second arg is an array')
+
+ const defs = {
+ a: 'def', c: 'def', d: undefined, e: null, f: undefined
+ }
+
+ const data = {
+ a: 'set', b: 'set', e: undefined, f: null, g: undefined
+ }
+
+ const result = defaultProps(defs, data)
+ const keyExists =
+ key => Object.keys(result).indexOf(key) !== -1
+
+ const getValue =
+ key => result[key]
+
+ t.notOk(keyExists('d'), 'undefined values in defaults not included')
+ t.notOk(keyExists('g'), 'undefined values in data not included')
+
+ t.equals(getValue('a'), 'set', 'data value overwrites values from defaults')
+ t.equals(getValue('b'), 'set', 'data value included when not on defalts')
+ t.equals(getValue('c'), 'def', 'default value included when not on data')
+ t.equals(getValue('e'), null, 'data value used when value undefined in defaults')
+ t.equals(getValue('f'), null, 'data value used when value undefined in defaults')
+
+ t.end()
+})
diff --git a/helpers/defaultTo.js b/helpers/defaultTo.js
new file mode 100644
index 000000000..1d40ed26d
--- /dev/null
+++ b/helpers/defaultTo.js
@@ -0,0 +1,12 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isNil = require('../predicates/isNil')
+
+// defaultTo : a -> b -> (a | b)
+function defaultTo(def, val) {
+ return isNil(val) ? def : val
+}
+
+module.exports = curry(defaultTo)
diff --git a/helpers/defaultTo.spec.js b/helpers/defaultTo.spec.js
new file mode 100644
index 000000000..408f1cbec
--- /dev/null
+++ b/helpers/defaultTo.spec.js
@@ -0,0 +1,27 @@
+const test = require('tape')
+
+const defaultTo = require('./defaultTo')
+
+test('defaultTo', t => {
+ const fn =
+ defaultTo('def')
+
+ const g =
+ () => 'func'
+
+ t.equal(fn(undefined), 'def', 'defaults an undefined value')
+ t.equal(fn(null), 'def', 'defaults a null value')
+ t.equal(fn(NaN), 'def', 'defaults a NaN value')
+ t.equal(fn(0), 0, 'does not default a falsey number')
+ t.equal(fn(1), 1, 'does not default a truthy number')
+ t.equal(fn(''), '', 'does not default a falsey string')
+ t.equal(fn('string'), 'string', 'does not default a truthy string')
+ t.equal(fn(false), false, 'does not default a false')
+ t.equal(fn(true), true, 'does not default a true')
+ t.equal(fn(g)(), 'func', 'does not default a function')
+ t.same(fn({}), {}, 'does not default an object')
+ t.same(fn([]), [], 'does not default an array')
+
+ t.end()
+})
+
diff --git a/helpers/fromPairs.js b/helpers/fromPairs.js
new file mode 100644
index 000000000..c97512d4c
--- /dev/null
+++ b/helpers/fromPairs.js
@@ -0,0 +1,36 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const isFoldable = require('../predicates/isFoldable')
+const isSameType = require('../predicates/isSameType')
+const isString = require('../predicates/isString')
+
+const Pair = require('../crocks/Pair')
+
+function foldPairs(acc, pair) {
+ if(!isSameType(Pair, pair)) {
+ throw new TypeError('fromPairs: Foldable of Pairs required for argument')
+ }
+
+ const key = pair.fst()
+ const value = pair.snd()
+
+ if(!isString(key)) {
+ throw new TypeError('fromPairs: String required for fst of every Pair')
+ }
+
+ return value !== undefined
+ ? Object.assign(acc, { [key]: value })
+ : acc
+}
+
+// fromPairs : Foldable f => f (Pair String a) -> Object
+function fromPairs(xs) {
+ if(!isFoldable(xs)) {
+ throw new TypeError('fromPairs: Foldable of Pairs required for argument')
+ }
+
+ return xs.reduce(foldPairs, {})
+}
+
+module.exports = fromPairs
diff --git a/helpers/fromPairs.spec.js b/helpers/fromPairs.spec.js
new file mode 100644
index 000000000..0796de777
--- /dev/null
+++ b/helpers/fromPairs.spec.js
@@ -0,0 +1,95 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const isObject = require('../predicates/isObject')
+
+const Pair = require('../crocks/Pair')
+
+const fromPairs = require('./fromPairs')
+
+test('fromPairs', t => {
+ const fn = bindFunc(fromPairs)
+
+ const noFold = /fromPairs: Foldable of Pairs required for argument/
+ t.throws(fn(undefined), noFold, 'throws when argument is undefined')
+ t.throws(fn(null), noFold, 'throws when argument is null')
+ t.throws(fn(0), noFold, 'throws when argument is a falsey number')
+ t.throws(fn(1), noFold, 'throws when argument is a truthy number')
+ t.throws(fn(''), noFold, 'throws when argument is a falsey string')
+ t.throws(fn('string'), noFold, 'throws when argument is a truthy string')
+ t.throws(fn(false), noFold, 'throws when argument is false')
+ t.throws(fn(true), noFold, 'throws when argument is true')
+ t.throws(fn(noop), noFold, 'throws when argument is a function')
+ t.throws(fn({}), noFold, 'throws when argument is an object')
+
+ t.throws(fn([ undefined ]), noFold, 'throws when argument contains undefined')
+ t.throws(fn([ null ]), noFold, 'throws when argument contains null')
+ t.throws(fn([ 0 ]), noFold, 'throws when argument contains a falsey number')
+ t.throws(fn([ 1 ]), noFold, 'throws when argument contains a truthy number')
+ t.throws(fn([ '' ]), noFold, 'throws when argument contains a falsey string')
+ t.throws(fn([ 'string' ]), noFold, 'throws when argument contains a truthy string')
+ t.throws(fn([ false ]), noFold, 'throws when argument contains false')
+ t.throws(fn([ true ]), noFold, 'throws when argument contains true')
+ t.throws(fn([ noop ]), noFold, 'throws when argument contains a function')
+ t.throws(fn([ {} ]), noFold, 'throws when argument contains an object')
+
+ const buildPair =
+ x => fn([ Pair(x, 0) ])
+
+ const noString = /fromPairs: String required for fst of every Pair/
+ t.throws(buildPair(undefined), noString, 'throws when fst of Pair is undefined')
+ t.throws(buildPair(null), noString, 'throws when fst of Pair is null')
+ t.throws(buildPair(0), noString, 'throws when fst of Pair is a falsey number')
+ t.throws(buildPair(1), noString, 'throws when fst of Pair is a truthy number')
+ t.throws(buildPair(false), noString, 'throws when fst of Pair is false')
+ t.throws(buildPair(true), noString, 'throws when fst of Pair is true')
+ t.throws(buildPair(noop), noString, 'throws when fst of Pair is a function')
+ t.throws(buildPair({}), noString, 'throws when fst of Pair is an object')
+ t.throws(buildPair([]), noString, 'throws when fst of Pair is an object')
+
+ const data = [
+ Pair('undef', undefined),
+ Pair('nil', null),
+ Pair('falseNumber', 0),
+ Pair('trueNumber', 1),
+ Pair('falseString', ''),
+ Pair('trueString', 'string'),
+ Pair('falseBoolean', false),
+ Pair('trueBoolean', true),
+ Pair('obj', {}),
+ Pair('array', [])
+ ]
+
+ const result = fromPairs(data)
+ const keys = Object.keys(result)
+ const hasKey = key => keys.indexOf(key) !== -1
+ const getValue = key => result[key]
+
+ t.ok(isObject(result), 'returns an object')
+
+ t.notOk(hasKey('undef'), 'does not include keys with an undefined value')
+ t.ok(hasKey('nil'), 'includes keys with a null value')
+ t.ok(hasKey('falseNumber'), 'includes keys with a falsey number value')
+ t.ok(hasKey('trueNumber'), 'includes keys with a truthy number value')
+ t.ok(hasKey('falseString'), 'includes keys with a falsey string value')
+ t.ok(hasKey('trueString'), 'includes keys with a truthy string value')
+ t.ok(hasKey('falseBoolean'), 'includes keys with a false value')
+ t.ok(hasKey('trueBoolean'), 'includes keys with a true value')
+ t.ok(hasKey('obj'), 'includes keys with an object value')
+ t.ok(hasKey('array'), 'includes keys with an array value')
+
+ t.equals(getValue('nil'), null, 'includes null values')
+ t.equals(getValue('falseNumber'), 0, 'includes falsey number values')
+ t.equals(getValue('trueNumber'), 1, 'includes truthy number values')
+ t.equals(getValue('falseString'), '', 'includes falsey string values')
+ t.equals(getValue('trueString'), 'string', 'includes truthy string values')
+ t.equals(getValue('falseBoolean'), false, 'includes false values')
+ t.equals(getValue('trueBoolean'), true, 'includes true values')
+ t.same(getValue('obj'), {}, 'includes object values')
+ t.same(getValue('array'), [], 'includes array values')
+
+ t.end()
+})
diff --git a/helpers/objOf.js b/helpers/objOf.js
new file mode 100644
index 000000000..e004142c7
--- /dev/null
+++ b/helpers/objOf.js
@@ -0,0 +1,16 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isString = require('../predicates/isString')
+
+// objOf: String -> a -> Object
+function objOf(key, value) {
+ if(!(key && isString(key))) {
+ throw new TypeError('objOf: Non-empty String required for first argument')
+ }
+
+ return { [key]: value }
+}
+
+module.exports = curry(objOf)
diff --git a/helpers/objOf.spec.js b/helpers/objOf.spec.js
new file mode 100644
index 000000000..57e8a49c4
--- /dev/null
+++ b/helpers/objOf.spec.js
@@ -0,0 +1,37 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const objOf = require('./objOf')
+
+test('objOf', t => {
+ const fn = bindFunc(objOf)
+
+ const err = /objOf: Non-empty String required for first argument/
+ t.throws(fn(undefined, {}), err, 'throws when first arg is undefined')
+ t.throws(fn(null, {}), err, 'throws when first arg is null')
+ t.throws(fn(0, {}), err, 'throws when first arg is a falsey number')
+ t.throws(fn(1, {}), err, 'throws when first arg is a truthy number')
+ t.throws(fn('', {}), err, 'throws when first arg is a falsey string')
+ t.throws(fn(false, {}), err, 'throws when first arg is false')
+ t.throws(fn(true, {}), err, 'throws when first arg is true')
+ t.throws(fn(noop, {}), err, 'throws when first arg is a function')
+ t.throws(fn({}, {}), err, 'throws when first arg is an object')
+ t.throws(fn([], {}), err, 'throws when first arg is an array')
+
+ t.same(objOf('key', undefined), { key: undefined }, 'allows for an undefined value')
+ t.same(objOf('key', null), { key: null }, 'allows for a null value')
+ t.same(objOf('key', 0), { key: 0 }, 'allows for a falsey number value')
+ t.same(objOf('key', 1), { key: 1 }, 'allows for a truthy number value')
+ t.same(objOf('key', ''), { key: '' }, 'allows for a falsey string value')
+ t.same(objOf('key', 'string'), { key: 'string' }, 'allows for a truthy string value')
+ t.same(objOf('key', false), { key: false }, 'allows for a false value')
+ t.same(objOf('key', true), { key: true }, 'allows for a true value')
+ t.same(objOf('key', noop), { key: noop }, 'allows for a function value')
+ t.same(objOf('key', {}), { key: {} }, 'allows for an object value')
+ t.same(objOf('key', []), { key: [] }, 'allows for an array value')
+
+ t.end()
+})
diff --git a/helpers/omit.js b/helpers/omit.js
new file mode 100644
index 000000000..19998a3a7
--- /dev/null
+++ b/helpers/omit.js
@@ -0,0 +1,28 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isFoldable = require('../predicates/isFoldable')
+const isObject = require('../predicates/isObject')
+
+function omitKeys(keys, obj) {
+ return function(acc, key) {
+ return keys.indexOf(key) === -1 && obj[key] !== undefined
+ ? Object.assign(acc, { [key]: obj[key] })
+ : acc
+ }
+}
+
+// omit : ([ String ] | List String) -> Object -> Object
+function omit(keys, obj) {
+ if(!isFoldable(keys)) {
+ throw new TypeError('omit: Foldable required for first argument')
+ }
+ else if(!isObject(obj)) {
+ throw new TypeError('omit: Object required for second argument')
+ }
+
+ return Object.keys(obj).reduce(omitKeys(keys, obj), {})
+}
+
+module.exports = curry(omit)
diff --git a/helpers/omit.spec.js b/helpers/omit.spec.js
new file mode 100644
index 000000000..ee03cd8f3
--- /dev/null
+++ b/helpers/omit.spec.js
@@ -0,0 +1,44 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const omit = require('./omit')
+
+test('omit', t => {
+ const fn = bindFunc(omit)
+
+ const noFold = /omit: Foldable required for first argument/
+ t.throws(fn(undefined, {}), noFold, 'throws when first arg is undefined')
+ t.throws(fn(null, {}), noFold, 'throws when first arg is null')
+ t.throws(fn(0, {}), noFold, 'throws when first arg is a falsey number')
+ t.throws(fn(1, {}), noFold, 'throws when first arg is a truthy number')
+ t.throws(fn('', {}), noFold, 'throws when first arg is a falsey string')
+ t.throws(fn('string', {}), noFold, 'throws when first arg is a truthy string')
+ t.throws(fn(false, {}), noFold, 'throws when first arg is false')
+ t.throws(fn(true, {}), noFold, 'throws when first arg is true')
+ t.throws(fn(noop, {}), noFold, 'throws when first arg is a function')
+ t.throws(fn({}, {}), noFold, 'throws when first arg is an object')
+
+ const noObj = /omit: Object required for second argument/
+ t.throws(fn([], undefined), noObj, 'throws when second arg is undefined')
+ t.throws(fn([], null), noObj, 'throws when second arg is null')
+ t.throws(fn([], 0), noObj, 'throws when second arg is a falsey number')
+ t.throws(fn([], 1), noObj, 'throws when second arg is a truthy number')
+ t.throws(fn([], ''), noObj, 'throws when second arg is a falsey string')
+ t.throws(fn([], 'string'), noObj, 'throws when second arg is a truthy string')
+ t.throws(fn([], false), noObj, 'throws when second arg is false')
+ t.throws(fn([], true), noObj, 'throws when second arg is true')
+ t.throws(fn([], noop), noObj, 'throws when second arg is a function')
+ t.throws(fn([], []), noObj, 'throws when second arg is an array')
+
+ const data = { a: 23, b: true, c: 'sea', d: undefined }
+
+ t.same(omit([], data), { a: 23, b: true, c: 'sea' }, 'removes undefineds')
+ t.same(omit([ 'a', 'c' ], data), { b: true }, 'omits provided keys from result')
+ t.same(omit([ 'blah' ], data), { a: 23, b: true, c: 'sea' }, 'returns new object without undefineds')
+ t.same(omit([ '' ], data), { a: 23, b: true, c: 'sea' }, 'removes undefineds when empty string passed')
+
+ t.end()
+})
diff --git a/helpers/pick.js b/helpers/pick.js
new file mode 100644
index 000000000..2a5de1f9b
--- /dev/null
+++ b/helpers/pick.js
@@ -0,0 +1,32 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const curry = require('./curry')
+const isFoldable = require('../predicates/isFoldable')
+const isObject = require('../predicates/isObject')
+const isString = require('../predicates/isString')
+
+function pickKeys(obj) {
+ return function(acc, key) {
+ if(!isString(key)) {
+ throw new TypeError('pick: Foldable of Strings is required for first argument')
+ }
+ return key && obj[key] !== undefined
+ ? Object.assign(acc, { [key]: obj[key] })
+ : acc
+ }
+}
+
+// pick : ([ String ] | List String) -> Object -> Object
+function pick(keys, obj) {
+ if(!isFoldable(keys)) {
+ throw new TypeError('pick: Foldable required for first argument')
+ }
+ else if(!isObject(obj)) {
+ throw new TypeError('pick: Object required for second argument')
+ }
+
+ return keys.reduce(pickKeys(obj), {})
+}
+
+module.exports = curry(pick)
diff --git a/helpers/pick.spec.js b/helpers/pick.spec.js
new file mode 100644
index 000000000..6cf47c179
--- /dev/null
+++ b/helpers/pick.spec.js
@@ -0,0 +1,55 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const pick = require('./pick')
+
+test('pick', t => {
+ const fn = bindFunc(pick)
+
+ const noFold = /pick: Foldable required for first argument/
+ t.throws(fn(undefined, {}), noFold, 'throws when first arg is undefined')
+ t.throws(fn(null, {}), noFold, 'throws when first arg is null')
+ t.throws(fn(0, {}), noFold, 'throws when first arg is a falsey number')
+ t.throws(fn(1, {}), noFold, 'throws when first arg is a truthy number')
+ t.throws(fn('', {}), noFold, 'throws when first arg is a falsey string')
+ t.throws(fn('string', {}), noFold, 'throws when first arg is a truthy string')
+ t.throws(fn(false, {}), noFold, 'throws when first arg is false')
+ t.throws(fn(true, {}), noFold, 'throws when first arg is true')
+ t.throws(fn(noop, {}), noFold, 'throws when first arg is a function')
+ t.throws(fn({}, {}), noFold, 'throws when first arg is an object')
+
+ const noObj = /pick: Object required for second argument/
+ t.throws(fn([], undefined), noObj, 'throws when second arg is undefined')
+ t.throws(fn([], null), noObj, 'throws when second arg is null')
+ t.throws(fn([], 0), noObj, 'throws when second arg is a falsey number')
+ t.throws(fn([], 1), noObj, 'throws when second arg is a truthy number')
+ t.throws(fn([], ''), noObj, 'throws when second arg is a falsey string')
+ t.throws(fn([], 'string'), noObj, 'throws when second arg is a truthy string')
+ t.throws(fn([], false), noObj, 'throws when second arg is false')
+ t.throws(fn([], true), noObj, 'throws when second arg is true')
+ t.throws(fn([], noop), noObj, 'throws when second arg is a function')
+ t.throws(fn([], []), noObj, 'throws when second arg is an array')
+
+ const noString = /pick: Foldable of Strings is required for first argument/
+ t.throws(fn([ undefined ], {}), noString, 'throws when first arg contains undefined')
+ t.throws(fn([ null ], {}), noString, 'throws when first arg contains null')
+ t.throws(fn([ 0 ], {}), noString, 'throws when first arg contains a falsey number')
+ t.throws(fn([ 1 ], {}), noString, 'throws when first arg contains a truthy number')
+ t.throws(fn([ false ], {}), noString, 'throws when first arg contains false')
+ t.throws(fn([ true ], {}), noString, 'throws when first arg contains true')
+ t.throws(fn([ noop ], { }), noString, 'throws when first arg contains a function')
+ t.throws(fn([ [] ], {}), noString, 'throws when first arg contains an array')
+ t.throws(fn([ {} ], {}), noString, 'throws when first arg contains an object')
+
+ const data = { a: 23, b: true, c: 'sea', d: undefined }
+
+ t.same(pick([ 'a', 'c', 'blah' ], data), { a: 23, c: 'sea' }, 'picks provided keys when keys exist')
+ t.same(pick([ 'blah' ], data), {}, 'returns an empty object when no key exists')
+ t.same(pick([ '' ], data), {}, 'returns an empty object when blank key exists')
+ t.same(pick([ 'd' ], data), {}, 'does not include keys if their values are undefined')
+
+ t.end()
+})
diff --git a/helpers/toPairs.js b/helpers/toPairs.js
new file mode 100644
index 000000000..26e7fab0f
--- /dev/null
+++ b/helpers/toPairs.js
@@ -0,0 +1,23 @@
+/** @license ISC License (c) copyright 2017 original and current authors */
+/** @author Ian Hofmann-Hicks (evil) */
+
+const isObject = require('../predicates/isObject')
+
+const List = require('../crocks/List')
+const Pair = require('../crocks/Pair')
+
+// toPairs : Object -> List (Pair String a)
+function toPairs(obj) {
+ if(!isObject(obj)) {
+ throw new TypeError('toPairs: Object required for argument')
+ }
+
+ return Object.keys(obj).reduce(
+ (acc, key) => obj[key] !== undefined
+ ? acc.concat(List.of(Pair(key, obj[key])))
+ : acc,
+ List.empty()
+ )
+}
+
+module.exports = toPairs
diff --git a/helpers/toPairs.spec.js b/helpers/toPairs.spec.js
new file mode 100644
index 000000000..b26017233
--- /dev/null
+++ b/helpers/toPairs.spec.js
@@ -0,0 +1,59 @@
+const test = require('tape')
+const helpers = require('../test/helpers')
+
+const bindFunc = helpers.bindFunc
+const noop = helpers.noop
+
+const isSameType = require('../predicates/isSameType')
+const List = require('../crocks/List')
+const Pair = require('../crocks/Pair')
+
+const toPairs = require('./toPairs')
+
+test('toPairs', t => {
+ const fn = bindFunc(toPairs)
+
+ const err = /toPairs: Object required for argument/
+ t.throws(fn(undefined), err, 'throws when argument is undefined')
+ t.throws(fn(null), err, 'throws when argument is null')
+ t.throws(fn(0), err, 'throws when argument is a falsey number')
+ t.throws(fn(1), err, 'throws when argument is a truthy number')
+ t.throws(fn(''), err, 'throws when argument is a falsey string')
+ t.throws(fn(false), err, 'throws when argument is false')
+ t.throws(fn(true), err, 'throws when argument is true')
+ t.throws(fn(noop), err, 'throws when argument is a function')
+ t.throws(fn([]), err, 'throws when argument is an array')
+
+ const data = {
+ undef: undefined,
+ nil: null,
+ falseNumber: 0,
+ trueNumber: 1,
+ falseString: '',
+ trueString: 'string',
+ falseBoolean: false,
+ trueBoolean: true,
+ obj: {},
+ array: []
+ }
+
+ const result = toPairs(data)
+ const allPairs = result.reduce((acc, x) => acc && isSameType(Pair, x), true)
+ const getPair = key => result.filter(x => x.fst() === key).head().option(Pair(null, 'No Key'))
+
+ t.ok(isSameType(List, result), 'returns a List')
+ t.ok(allPairs, 'list contains all Pairs')
+
+ t.equals(getPair('undef').snd(), 'No Key', 'does not include keys with undefined values')
+ t.equals(getPair('nil').snd(), null, 'includes keys with null values')
+ t.equals(getPair('falseNumber').snd(), 0, 'includes keys with falsey number values')
+ t.equals(getPair('trueNumber').snd(), 1, 'includes keys with truthy number values')
+ t.equals(getPair('falseString').snd(), '', 'includes keys with falsey string values')
+ t.equals(getPair('trueString').snd(), 'string', 'includes keys with truthy string values')
+ t.equals(getPair('falseBoolean').snd(), false, 'includes keys with false boolean values')
+ t.equals(getPair('trueBoolean').snd(), true, 'includes keys with true boolean values')
+ t.same(getPair('array').snd(), [], 'includes keys with array values')
+ t.same(getPair('obj').snd(), {}, 'includes keys with object values')
+
+ t.end()
+})
diff --git a/internal/object.js b/internal/object.js
index 9a6edefe4..a43c2089d 100644
--- a/internal/object.js
+++ b/internal/object.js
@@ -1,6 +1,22 @@
/** @license ISC License (c) copyright 2017 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */
+function rejectUnit(obj) {
+ return function(acc, key) {
+ const value = obj[key]
+
+ if(value !== undefined) {
+ acc[key] = value
+ }
+ return acc
+ }
+}
+
+function assign(x, m) {
+ const result = Object.keys(m).reduce(rejectUnit(m), {})
+ return Object.keys(x).reduce(rejectUnit(x), result)
+}
+
function filter(f, m) {
return Object.keys(m).reduce((acc, key) => {
if(f(m[key])) {
@@ -10,6 +26,15 @@ function filter(f, m) {
}, {})
}
+function map(f, m) {
+ return Object.keys(m).reduce((acc, key) => {
+ acc[key] = f(m[key])
+ return acc
+ }, {})
+}
+
module.exports = {
- filter
+ assign,
+ filter,
+ map
}
diff --git a/internal/object.spec.js b/internal/object.spec.js
index da83670a7..2b6649c84 100644
--- a/internal/object.spec.js
+++ b/internal/object.spec.js
@@ -1,8 +1,41 @@
const test = require('tape')
-const object= require('./object')
+const composeB = require('../combinators/composeB')
+const identity = require('../combinators/identity')
+const object = require('./object')
+
+const assign = object.assign
const filter = object.filter
+const map = object.map
+
+test('object assign functionality', t => {
+ const first = {
+ a: 'first', c: 'first', d: undefined, e: null, f: undefined
+ }
+
+ const second = {
+ a: 'second', b: 'second', e: undefined, f: null, g: undefined
+ }
+
+ const result = assign(first, second)
+ const keyExists =
+ key => Object.keys(result).indexOf(key) !== -1
+
+ const getValue =
+ key => result[key]
+
+ t.notOk(keyExists('d'), 'undefined values in first arg not included')
+ t.notOk(keyExists('g'), 'undefined values in second arg not included')
+
+ t.equals(getValue('a'), 'first', 'first arg value overwrites values from second arg')
+ t.equals(getValue('b'), 'second', 'second arg value included when not on first arg')
+ t.equals(getValue('c'), 'first', 'first arg value included when not on second arg')
+ t.equals(getValue('e'), null, 'second arg used when value undefined in first arg')
+ t.equals(getValue('f'), null, 'first arg used when value undefined in second arg')
+
+ t.end()
+})
test('object filter functionality', t => {
const obj = { a: 'cat', b: 'great', d: 'dog' }
@@ -12,3 +45,30 @@ test('object filter functionality', t => {
t.same(filter(fn, {}), {}, 'filters as expected when empty')
t.end()
})
+
+test('object map functionality', t => {
+ const m = {
+ a: undefined, b: null,
+ c: 0, d: 1,
+ e: '', f: 'string',
+ g: false, h: true,
+ i: {}, j: [],
+ }
+
+
+ t.same(map(identity, m), m, 'allows all values types')
+
+ t.end()
+})
+
+test('object map properties (Functor)', t => {
+ const m = { a: 23, b: 44, c: 99 }
+
+ const f = x => x + 23
+ const g = x => x * 2
+
+ t.same(map(identity, m), m, 'identity')
+ t.same(map(composeB(f, g), m), map(f, map(g, m)), 'composition')
+
+ t.end()
+})
diff --git a/monoids/Assign.js b/monoids/Assign.js
index 3b642b9c2..de14383d2 100644
--- a/monoids/Assign.js
+++ b/monoids/Assign.js
@@ -9,6 +9,8 @@ const _inspect = require('../internal/inspect')
const constant = require('../combinators/constant')
+const object = require('../internal/object')
+
const _empty =
() => Assign({})
@@ -39,7 +41,7 @@ function Assign(o) {
throw new TypeError('Assign.concat: Assign required')
}
- return Assign(Object.assign({}, x, m.value()))
+ return Assign(object.assign(m.value(), x))
}
return { inspect, value, type, concat, empty }
diff --git a/package.json b/package.json
index 01fc79533..9cdd498a5 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.8.0",
+ "babel-plugin-transform-es2015-computed-properties": "^6.24.1",
"babel-plugin-transform-es2015-shorthand-properties": "^6.8.0",
"babel-plugin-transform-es2015-template-literals": "^6.8.0",
"coveralls": "^2.11.15",
diff --git a/pointfree/map.js b/pointfree/map.js
index 7d0d6886d..be7235085 100644
--- a/pointfree/map.js
+++ b/pointfree/map.js
@@ -4,9 +4,11 @@
const composeB = require('../combinators/composeB')
const curry = require('../helpers/curry')
-const _map = require('../internal/array').map
+const array = require('../internal/array')
+const object = require('../internal/object')
const isArray = require('../predicates/isArray')
+const isObject = require('../predicates/isObject')
const isFunction = require('../predicates/isFunction')
// map :: Functor f => (a -> b) -> f a -> f b
@@ -18,13 +20,16 @@ function map(fn, m) {
return composeB(fn, m)
}
else if(isArray(m)) {
- return _map(fn, m)
+ return array.map(fn, m)
}
else if(m && isFunction(m.map)) {
return m.map(fn)
}
+ else if(isObject(m)) {
+ return object.map(fn, m)
+ }
else {
- throw new TypeError('map: Function or Functor of the same type required for second requirement')
+ throw new TypeError('map: Object, Function or Functor of the same type required for second requirement')
}
}
diff --git a/pointfree/map.spec.js b/pointfree/map.spec.js
index 5bc15ed3b..ecafed7d8 100644
--- a/pointfree/map.spec.js
+++ b/pointfree/map.spec.js
@@ -29,7 +29,7 @@ test('map pointfree', t => {
t.throws(m([], f), noFunc, 'throws if first arg is an array')
t.throws(m({}, f), noFunc, 'throws if first arg is an object')
- const noFunctor = /map: Function or Functor of the same type required for second requirement/
+ const noFunctor = /map: Object, Function or Functor of the same type required for second requirement/
t.throws(m(noop, undefined), noFunctor, 'throws if second arg is undefined')
t.throws(m(noop, null), noFunctor, 'throws if second arg is null')
t.throws(m(noop, 0), noFunctor, 'throws if second arg is a falsey number')
@@ -38,7 +38,6 @@ test('map pointfree', t => {
t.throws(m(noop, 'string'), noFunctor, 'throws if second arg is a truthy string')
t.throws(m(noop, false), noFunctor, 'throws if second arg is false')
t.throws(m(noop, true), noFunctor, 'throws if second arg is true')
- t.throws(m(noop, {}), noFunctor, 'throws if second arg is an object')
t.end()
})
@@ -76,3 +75,12 @@ test('map function composition', t => {
t.end()
})
+
+test('map object', t => {
+ const f = x => x + 1
+ const m = { a: 3, b: 4 }
+
+ t.same(map(f, m), { a: 4, b: 5 }, 'applies function to each element in the object')
+
+ t.end()
+})
diff --git a/predicates/isNil.js b/predicates/isNil.js
index 6eaf1ed42..57ef5222f 100644
--- a/predicates/isNil.js
+++ b/predicates/isNil.js
@@ -3,7 +3,7 @@
// isNil :: a -> Boolean
function isNil(x) {
- return x === undefined || x === null
+ return x === undefined || x === null || Number.isNaN(x)
}
module.exports = isNil
diff --git a/predicates/isNil.spec.js b/predicates/isNil.spec.js
index 6286ceb68..905f2447a 100644
--- a/predicates/isNil.spec.js
+++ b/predicates/isNil.spec.js
@@ -21,6 +21,7 @@ test('isNil predicate function', t => {
t.equal(isNil(undefined), true, 'returns true with undefined')
t.equal(isNil(null), true, 'returns true with null')
+ t.equal(isNil(NaN), true, 'returns true with NaN')
t.end()
})