From 5317483248ea2188334fdce80ad5df30979688be Mon Sep 17 00:00:00 2001 From: Ian Hofmann-Hicks Date: Sun, 18 Feb 2018 01:06:38 -0800 Subject: [PATCH] add pair readme --- docs/src/pages/docs/crocks/Pair.md | 1021 +++++++++++++++++ docs/src/pages/docs/crocks/index.md | 18 +- docs/src/pages/docs/functions/helpers.md | 37 +- docs/src/pages/docs/functions/index.md | 6 +- .../docs/functions/pointfree-functions.md | 50 +- 5 files changed, 1077 insertions(+), 55 deletions(-) create mode 100644 docs/src/pages/docs/crocks/Pair.md diff --git a/docs/src/pages/docs/crocks/Pair.md b/docs/src/pages/docs/crocks/Pair.md new file mode 100644 index 000000000..7b76f5b5a --- /dev/null +++ b/docs/src/pages/docs/crocks/Pair.md @@ -0,0 +1,1021 @@ +--- +title: "Pair" +description: "Canonical Product Type" +layout: "guide" +functions: ["branch", "fst", "merge", "snd", "topairs", "writertopair"] +weight: 100 +--- + +```haskell +Pair a b +``` + +`Pair` allows the ability to represent (2) distinct values of different types. +Much like how `Either` is known as canonical Sum Type and defines the basis for +all other Sum Types that ship with `crocks`, `Pair` is known as the canonical +Product Type and also at the heart of all Product Types in `crocks`. + +As `Pair` is a `Bifunctor`, it can vary in each of the (2) types it represents. +When used as a normal `Functor`, `Pair` will always have a bias for the far +right or second value, matching the pattern of the other ADTs in `crocks`. When +mapped with a function, the function will only be applied to the second value, +and will leave the first value untouched. + +`Pair` also provides the ability to use `ap` and `chain`, but in order to +combine the resulting instances in a predictable, repeatable fashion the first +values in the `Pair`s must be `Semigroup` instances of the same type. When +applied, `ap` and `chain` will concatenate the `Semigroup`s providing the result +of the concatenation in the first position of the resulting `Pair`. + +A helpful benefit of the `Bifunctor` aspects `Pair` allows for defining parallel +computations. There are many functions that ship with `crocks` that allow for +parallelization such as [`branch`](#branch), [`merge`](#merge-pointfree) and +`fanout`. Using those helpers in conjunction with the ability to +[`bimap`](#bimap) functions over a given `Pair`s values. + +```javascript +import Pair from 'crocks/Pair' +import Sum from 'crocks/Sum' +import bimap from 'crocks/pointfree/bimap' +import branch from 'crocks/Pair/branch' +import compose from 'crocks/helpers/compose' +import mreduce from 'crocks/helpers/mreduce' +import merge from 'crocks/Pair/merge' + +// negate :: a -> Boolean +const negate = + x => !x + +// inc :: Number -> Number +const inc = + x => x + 1 + +// length :: Array -> Number +const length = + x => x.length + +// divide :: Number -> Number +const divide = + (x, y) => x / y + +Pair(76, false) + .bimap(inc, negate) +//=> Pair(77, true) + +// average :: [ Number ] -> Number +const average = compose( + merge(divide), + bimap(mreduce(Sum), length), + branch +) + +average([ 9, 77, 34 ]) +//=> 40 +``` + +
+ +## Implements +`Setoid`, `Semigroup`, `Functor`, `Bifunctor`, `Apply`, `Chain`, +`Traversable`, `Extend` + +
+ +
+ +## Instance Methods + +#### equals + +```haskell +Pair a b ~> c -> Boolean +``` + +Used to compare the underlying values of (2) `Pair` instances for equality by +value. `equals` takes any given argument and returns `true` if the passed +arguments is a `Pair` with an underlying values both in the first and second are +equal to the underlying values in the first and second of the `Maybe` the +method is being called on. If the passed argument is not a `Maybe` or the +underlying values are not equal, `equals` will return `false`. + +```javascript +import Pair from 'crocks/Pair' +import equals from 'crocks/pointfree/equals' + +Pair({ num: 33 }, 'string') + .equals(Pair({ num: 33 }, 'string')) +//=> true + +Pair({ num: 33 }, 'string') + .equals(Pair({ num: 10 }, 'string')) +//=> false + +Pair({ num: 33 }, 'string') + .equals(Pair({ num: 33 }, 'different')) +//=> false + +equals(Pair([ 1, 2 ], ''), [ 1, 2 ]) +//=> false +``` + +#### concat + +```haskell +Semigroup s, t => Pair s t ~> Pair s t -> Pair s t +``` + +When both underlying values of a given `Pair` are fixed to a `Semigroup`, +`concat` can be used to concatenate another `Pair` instance with underlying +`Semigroup`s of the same type and structure. Expecting a `Maybe` wrapping a +`Semigroup` of the same type, `concat` will give back a new `Pair` instance +wrapping the result of combining the underlying `Semigroup` instances. + +```javascript +import Pair from 'crocks/Pair' +import Maybe from 'crocks/Maybe' +import Sum from 'crocks/Sum' + +import compose from 'crocks/helpers/compose' +import concat from 'crocks/pointfree/concat' +import fanout from 'crocks/helpers/fanout' +import flip from 'crocks/combinators/flip' +import map from 'crocks/pointfree/map' +import mapReduce from 'crocks/helpers/mapReduce' +import option from 'crocks/pointfree/option' +import prop from 'crocks/Maybe/prop' + +Pair(Sum(3), [ 3 ]) + .concat(Pair(Sum(10), [ 10 ])) +//=> Pair( Sum 13, [ 3, 10 ] ) + +// Person :: { name: String, age: Number } +// peeps :: [ Person ] +const peeps = [ + { name: 'Haskell', age: 82 }, + { name: 'Heinrich', age: 81 }, + { name: 'Maria', age: 93 } +] + +// mapProp :: (String, (a -> b)) -> Object -> Maybe b +const mapProp = (key, fn) => + compose(map(fn), prop(key)) + +// Combined :: Pair (Maybe [ String ]) (Maybe Sum) +// splitPerson :: Person -> Combined +const splitPerson = fanout( + mapProp('name', x => [ x ]), + mapProp('age', Sum) +) + +// empty :: Combined +const empty = + Pair(Maybe([]), Maybe(Sum.empty())) + +// combine :: [ Person ] -> Combined +const combine = mapReduce( + splitPerson, + flip(concat), + empty +) + +combine(peeps) + .bimap(option([]), option(Sum(0))) +//=> Pair( [ "Haskell", "Heinrich", "Maria" ], Sum 256 ) +``` + +#### map + +```haskell +Pair c a ~> (a -> b) -> Pair c b +``` + +Used to apply transformations to values to the second portion of a given `Pair` +instance. `map` takes a function that it will lift into the context of the +`Pair` and apply to it second value in the `Pair`, returning a new `Pair` +instance. The new instance will contain the result of mapping in the second, +leaving the value in the first untouched. If you need to map the first value, +[`bimap`](#bimap) can be used instead. + +```javascript +import Pair from 'crocks/Pair' + +import compose from 'crocks/helpers/compose' +import map from 'crocks/pointfree/map' +import merge from 'crocks/Pair/merge' +import objOf from 'crocks/helpers/objOf' + +// length :: String -> Number +const length = + x => x.length + +// add10 :: Number -> Number +const add10 = + x => x + 10 + +// keyedLength :: Pair String String -> Object +const keyedLength = + compose(merge(objOf), map(length)) + +Pair('number', 32) + .map(add10) +//=> Pair("number", 42) + +keyedLength( + Pair('text', 'This is some text') +) +//=> { text: 17 } +``` + +#### bimap + +```haskell +Pair a b ~> ((a -> c), (b -> d)) -> Pair c d +``` + +The types and values that make up a `Pair` can vary independently in both the +first and second portions of the `Pair`. While [`map`](#map) can be used to +apply these transformations, `bimap` allows for independent transformations +on both sides, in parallel. + +`bimap` takes (2) mapping functions as its arguments. The first function is used +to map the first, while the second maps the second. `Pair` only provides a +means to map the second's value exclusively using [`map`](#map). If the need +arises to map the first portion exclusively, use `bimap` passing the mapping +function to the first argument and an [`identity`][identity] to the second. + +```javascript +import Pair from 'crocks/Pair' +import Sum from 'crocks/Sum' + +import compose from 'crocks/helpers/compose' +import bimap from 'crocks/pointfree/bimap' +import branch from 'crocks/Pair/branch' +import identity from 'crocks/combinators/identity' +import merge from 'crocks/Pair/merge' +import mreduce from 'crocks/helpers/mreduce' + +// add10 :: Number -> Number +const add10 = + x => x + 10 + +// divide :: (Number, Number) -> Number +const divide = + (x, y) => x / y + +// length :: [ Number ] -> Number +const length = + x => x.length + +// average :: [ Number ] -> Number +const average = compose( + merge(divide), + bimap(mreduce(Sum), length), + branch +) + +Pair(35, 'number') + .bimap(add10, identity) +//=> Pair( 45, 'number' ) + +average([ 2, 3, 4 ]) +//=> 3 +``` + +#### ap + +```haskell +Semigroup s => Pair s (a -> b) ~> Pair s a -> Pair s b +``` + +Short for apply, `ap` is used to apply a `Pair` instance containing a value on +its second portion to another `Pair` instance that contains a function in its +second portion. The result of this application provides a new `Pair` instance +containing the result in the second portion. `ap` requires that it is called on +an instance that wraps a curried polyadic function in the second. + +An additional constraint when using `ap` is that the `Pair` must contain a +`Semigroup` instance in its first. This is required for both the `Pair` with +the function and the `Pair` with the value to be applied. With both `Semigroups` +being of the same type. + +```javascript +import Pair from 'crocks/Pair' +import liftA2 from 'crocks/helpers/liftA2' + +// add :: Number -> Number -> Number +const add = + x => y => x + y + +// NumberRec :: Pair [ Number ] Number + +// twentyThree :: NumberRec +const twentyThree = + Pair([ 23 ], 23) + +// seventySeven :: NumberRec +const seventySeven = + Pair([ 77 ], 77) + +// combine :: NumberRec -> NumberRec -> NumberRec +const combine = + liftA2(add) + +twentyThree + .map(add) + .ap(seventySeven) +// Pair( [ 23, 77 ], 100 ) + +combine(twentyThree, seventySeven) +// Pair( [ 23, 77 ], 100 ) +``` + +#### chain + +```haskell +Semigroup s => Pair s a ~> (a -> Pair s b) -> Pair s b +``` + +Combining a sequential series of transformations that allows for custom +accumulation in addition to transforming a value. `chain` requires a `Pair` +returning function that contains a `Semigroup` in its first position. As an +additional requirement, is that instances of the same `Semigroup` must occupy +the first position of the source `Pair` and the `Pair` returned by the function. + +```javascript +const Pair = require('crocks/Pair') + +const assoc = require('crocks/helpers/assoc') +const omit = require('crocks/helpers/omit') + +// addTmp :: (String, a, Object) -> Pair [ String ] Object +const addTmp = (key, value, x) => + Pair([ key ], assoc(key, value, x)) + +// add :: Object -> Pair [ String ] Object +const add = data => { + const { a, b } = data + return addTmp('sum', a + b, data) +} + +// multiply :: Object -> Pair [ String ] Object +const multiply = data => { + const { a, b } = data + return addTmp('product', a * b, data) +} + +// calc :: Object -> Object +const calc = data => { + const { product, sum } = data + return assoc('result', product - sum, data) +} + +// flow :: Object -> Object +const flow = x => + Pair([], x) + .chain(add) + .chain(multiply) + .map(calc) + .merge(omit) + +flow({ a: 34, b: 76 }) +//=> { a: 34, b: 76, result: 2474 } + +flow({ a: 10, b: 5 }) +//=> { a: 10, b: 5, result: 35 } +``` + +#### sequence + +```haskell +Applicative TypeRep t, Apply f => Pair a (f b) ~> (t | (b -> f b)) -> f (Pair a b) +``` + +When an instance of `Pair` wraps an `Apply` instance in its second position, +`sequence` can be used to swap the type sequence. `sequence` requires either an +`Applicative TypeRep` or an `Apply` returning function is provided for its +argument. + +While it is not a requirement that the first position be occupied by a +`Semigroup`, in having an instance there sequencing back on a data structure +with multiple items can allow for accumulation then sequencing back. + +`sequence` can be derived from [`traverse`](#traverse) by passing it an +[`identity`][identity] function (`x => x`). + +```javascript +import Pair from 'crocks/Pair' +import Sum from 'crocks/Sum' + +import bimap from 'crocks/pointfree/bimap' +import compose from 'crocks/helpers/compose' +import concat from 'crocks/pointfree/concat' +import flip from 'crocks/combinators/flip' +import mapReduce from 'crocks/helpers/mapReduce' +import sequence from 'crocks/pointfree/sequence' + +// pair :: Pair Number [ String ] +const pair = + Pair(1, [ 'a', 'b', 'c' ]) + +// empty :: () -> Pair Sum String +const empty = + () => Pair(Sum.empty(), '') + +// seqArray :: Traversable t => t [ a ] -> [ t a ] +const seqArray = + sequence(Array) + +// toUpper :: String -> String +const toUpper = + x => x.toUpperCase() + +// combine :: [ Pair Number String ] -> Pair Sum String +const combine = mapReduce( + bimap(Sum, toUpper), + flip(concat), + empty() +) + +// flow :: Pair Number [ String ] -> Pair Sum String +const flow = + compose(combine, seqArray) + +seqArray(pair) +//=> [ Pair(1, "a"), Pair(1, "b"), Pair(1, "c") ] + +flow(pair) +//=> Pair(Sum 3, "ABC") +``` + +#### traverse + +```haskell +Applicative TypeRep t, Apply f => Pair a b ~> ((t | (b -> f b)), (b -> f c)) -> f (Pair a c) +``` + +Used to apply the "effect" of an `Apply` to a value in the second position of +a `Pair`, `traverse` combines both the "effects" of the `Apply` and the `Pair` +by returning a new instance of the `Apply`, wrapping the result of the +`Apply`s "effect" on the value in the second position of the `Pair`. + +`traverse` requires either an `Applicative TypeRep` or an `Apply` returning +function as its first argument and a function that is used to apply the "effect" +of the target `Apply` to the value in the second position of the `Pair`. The +"effect" will only be applied to second value and leaves the first value +untouched. Both arguments must return an instance of the target `Apply`. + +```javascript +import Maybe from 'crocks/Maybe' +import Pair from 'crocks/Pair' + +import identity from 'crocks/combinators/identity' +import safe from 'crocks/Maybe/safe' +import traverse from 'crocks/pointfree/traverse' + +const { Just, Nothing } = Maybe + +// isOdd :: Integer -> Boolean +const isOdd = + x => !!(x % 2) + +// safeOdd :: Traversable t => t Integer -> Maybe (t Integer) +const safeOdd = + traverse(Maybe, safe(isOdd)) + +// seqMaybe :: Traversable t => t (Maybe a) -> Maybe (t a) +const seqMaybe = + traverse(Maybe.of, identity) + +// odd :: Pair [ Number ] Integer +const odd = + Pair([ 10 ], 23) + +// even :: Pair String Integer +const even = + Pair('nope', 42) + +safeOdd(odd) +//=> Just Pair( [ 10 ], 23 ) + +safeOdd(even) +//=> Nothing + +seqMaybe(Pair(true, Just('good'))) +//=> Just Pair( true, "good" ) + +seqMaybe(Pair(false, Nothing())) +//=> Nothing +``` + +#### extend + +```haskell +Pair a b ~> (Pair a b -> c) -> Pair a c +``` + +Used map the second position of a given `Pair` instance by taking the entire +`Pair` into consideration. `extend` takes a function the receives a `Pair` as +its input and returns a new `Pair` with the result of that function in the +second position, while leaving the value in the first position untouched. + +```javascript +import Pair from 'crocks/Pair' + +import extend from 'crocks/pointfree/extend' +import merge from 'crocks/Pair/merge' +import objOf from 'crocks/helpers/objOf' + +// name :: Pair String String +const name = + Pair('name', 'Thomas') + +// mergeObj :: Pair String a -> Object +const mergeObj = + merge(objOf) + +// makeObj :: Pair String a -> Pair String Object +const makeObj = + extend(mergeObj) + +makeObj(name) +//=> Pair("name", { name: "Thomas" }) +``` + +#### swap + +```haskell +Pair a b ~> ((a -> c), (b -> d)) -> Pair d c +``` + +Used to map the value of a `Pair`s first position into the second position and +the second position into the first, `swap` takes (2) functions as its arguments. +The first function is used to map the value in the first position to the second, +while the second 'maps the second into the first'. If no mapping is required on +either side, then [`identity`][identity] functions can be used in one or both arguments. + +```javascript +import Pair from 'crocks/Pair' + +import identity from 'crocks/combinators/identity' +import swap from 'crocks/pointfree/swap' + +// toString :: a -> String +const toString = + x => x.toString() + +// swapMap :: Pair a String -> Pair Number String +const swapMap = + swap(toString, parseInt) + +// m :: Pair Number String +const m = + Pair(76, '105') + +m.swap(identity, identity) +//=> Pair("105", 76) + +swapMap(m) +//=> Pair(105, "76") +``` + +#### fst + +```haskell +Pair a b ~> () -> a +``` + +`fst` is one of (2) projection methods used to extract the values contained in +a given `Pair` instance. `fst` takes nothing as its input and will unwrap and +provide the value in the first position, throwing away the value in the second. +[`snd`](#snd) is the other projection function provided and is used to extract +the value in the second position. + +```javascript +import Pair from 'crocks/Pair' + +Pair('left', 'right') + .fst() +//=> "left" +``` + +#### snd + +```haskell +Pair a b ~> () -> b +``` + +`snd` is one of (2) projection methods used to extract the values contained in +a given `Pair` instance. `snd` takes nothing as its input and will unwrap and +provide the value in the second position, throwing away the value in the first. +[`fst`](#fst) is the other projection function provided and is used to extract +the value in the first position. + +```javascript +import Pair from 'crocks/Pair' + +Pair('left', 'right') + .snd() +//=> "right" +``` + +#### toArray + +```haskell +Pair a b ~> () -> [ a, b ] +``` + +While both [`fst`](#fst) and [`snd`](#snd) can be used to extract specific +values out of the structure of `Pair`, `toArray` extracts values but +maintains the structure. Taking nothing as its input, `toArray` will return an +`Array` of two values. The first value in the `Pair` will occupy the [0] index, +while the [1] index will house the second. + +```javascript +import Pair from 'crocks/Pair' +import compose from 'crocks/helpers/compose' + +// toArray :: Pair a b -> [ a, b ] +const toArray = + x => x.toArray() + +// toObject :: [ a, b ] -> Object +const toObject = + ([ left, right ]) => ({ left, right }) + +// pairToObject :: Pair a b -> Object +const pairToObject = + compose(toObject, toArray) + +// m :: Pair String Number +const m = + Pair('a', 1) + +m.toArray() +//=> [ 'a', 1 ] + +pairToObject(m) +//=> { left: 'a', right: 1 } +``` + +#### merge + +```haskell +Pair a b ~> ((a, b) -> c) -> c +``` + +Acting as a means to fold a given `Pair` over a binary operation, `merge` takes +a binary function as its sole argument. Using the function, `merge` will unwrap +each of its values and apply them to the function in order from first to second. +The result of the provided function is then provided as the overall result +for `merge`. + +This method comes in handy when using a `Pair` as a means to run parallel +computations and combine their results into a final answer. Typically this +method works hand in hand with the either the [`branch`](#branch) or +[`fanout`][fanout] helper functions. + +```javascript +import Sum from 'crocks/Sum' + +import compose from 'crocks/helpers/compose' +import fanout from 'crocks/helpers/fanout' +import merge from 'crocks/Pair/merge' +import mreduce from 'crocks/helpers/mreduce' + +// length :: [ a ] -> Integer +const length = + x => x.length + +// divide :: (Number, Number) -> Number +const divide = + (x, y) => x / y + +// average :: [ Number ] -> Number +const average = compose( + merge(divide), + fanout(mreduce(Sum), length) +) + +// nums :: [ Number ] +const nums = + [ 23, 96, 90, 4, 21 ] + +average(nums) +// 46.8 +``` + +
+ +
+ +## Helper Functions + +#### branch + +`crocks/Pair/branch` + +```haskell +branch :: a -> Pair a a +``` + +Typically the starting point for handling parallel computations on a single +value, `branch` takes a single value of any type as its only argument. `branch` +then returns a `Pair` with the reference or value in both the first and second +positions. + +Using `branch` can simplify how computations that depend on the same value are +constructed and encoded by removing the need to keep the original value in some +state that needs to be passed from function to function. + +```javascript +import assign from 'crocks/helpers/assign' +import bimap from 'crocks/pointfree/bimap' +import branch from 'crocks/Pair/branch' +import compose from 'crocks/helpers/compose' +import curry from 'crocks/helpers/curry' +import objOf from 'crocks/helpers/objOf' +import merge from 'crocks/Pair/merge' + +// add10 :: Number -> Number +const add10 = + x => x + 10 + +// applyChange :: (a -> b) -> a -> Object +const applyChange = fn => + compose(objOf('current'), fn) + +// createUndo :: (a -> b) -> a -> Object +const createUndo = curry(fn => + compose( + merge(assign), + bimap(objOf('orig'), applyChange(fn)), + branch + ) +) + +// applyAdd10 :: Number -> Object +const applyAdd10 = + createUndo(add10) + +applyAdd10(5) +// { current: 15, orig: 5 } +``` + +#### toPairs + +`crocks/Pair/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]. + +```javascript +import compose from 'crocks/helpers/compose' +import map from 'crocks/pointfree/map' +import merge from 'crocks/Pair/merge' +import toPairs from 'crocks/Pair/toPairs' + +// record :: Object +const record = { + firstName: 'Joey', + lastName: 'Fella', + age: 34 +} + +// joinField :: (String, a) -> String +const joinField = (key, value) => + `${key}:${value}` + +// joinRecord :: List String -> String +const joinRecord = list => + list.toArray() + .join('|') + +// buildRecord :: Object -> String +const buildRecord = compose( + joinRecord, + map(merge(joinField)), + toPairs +) + +buildRecord(record) +//=> "firstName:Joey|lastName:Fella|age:34" +``` + +
+ +
+ +## Pointfree Functions + +#### fst (pointfree) + +`crocks/Pair/fst` + +```haskell +fst :: Pair a b -> a +``` + +The `fst` pointfree function is used extract the leftmost value of a `Pair` by +invoking the [`fst`](#fst) method on a given instance, returning the result. +`fst` takes a `Pair` as its only argument and returns the value wrapped in the +leftmost portion of the provided `Pair`. + +```javascript +import Pair from 'crocks/Pair' + +import flip from 'crocks/combinators/flip' +import fst from 'crocks/Pair/fst' +import ifElse from 'crocks/logic/ifElse' +import merge from 'crocks/Pair/merge' +import snd from 'crocks/Pair/snd' + +// lte :: (Number, Number) -> Boolean +const lte = + (y, x) => x <= y + +// min :: Pair Number Number -> Number +const min = ifElse( + merge(flip(lte)), + fst, + snd +) + +min(Pair(1, 2)) +//=> 1 + +min(Pair(45, 22)) +//=> 22 + +min(Pair(100, 100)) +//=> 100 +``` + +#### merge (pointfree) + +`crocks/Pair/merge` + +```haskell +merge :: ((a, b) -> c) -> Pair a b -> c +``` + +The pointfree `merge` function allows the ability to fold out a new value, using +a binary function to combine the two values into one. This function takes (2) +arguments, with the first being a binary function used to combine a `Pair`s +values and the second is the instance of `Pair` for the function to be applied. +Like the method on `Pair`, this function will return the result of the provided +binary function. + +```javascript +import Pair from 'crocks/Pair' + +import assoc from 'crocks/helpers/assoc' +import compose from 'crocks/helpers/compose' +import curry from 'crocks/helpers/curry' +import map from 'crocks/pointfree/map' +import merge from 'crocks/Pair/merge' + +// mergeObj :: Pair String a -> Object -> Object +const mergeObj = + merge(assoc) + +// inc :: Number -> Number +const inc = + x => x + 1 + +// incAssign :: Pair String Number -> Object -> Object +const incAssign = curry( + compose(mergeObj, map(inc)) +) + +incAssign(Pair('apples', 5), {}) +// { apples: 6 } + +incAssign( + Pair('num', 57), + { string: 'a string' } +) +// { string: 'a string', num: 58 } +``` + +#### snd (pointfree) + +`crocks/Pair/snd` + +```haskell +snd :: Pair a b -> b +``` + +The `snd` pointfree function is used extract the rightmost value of a `Pair` by +invoking the [`snd`](#snd) method on a given instance, returning the result. +`snd` takes a `Pair` as its only argument and returns the value wrapped in the +rightmost portion of the provided `Pair`. + +```javascript +import Pair from 'crocks/Pair' +import Unit from 'crocks/Unit' + +import chain from 'crocks/pointfree/chain' +import compose from 'crocks/helpers/compose' +import map from 'crocks/pointfree/map' +import snd from 'crocks/Pair/snd' + +// Box :: a -> Pair () a +const Box = + x => Pair(Unit(), x) + +// unbox :: Pair a b -> b +const unbox = + snd + +// add :: Number -> Number -> Number +const add = + x => y => x + y + +// doubleBoxed :: Number -> Pair () Number +const doubleBoxed = + m => Box(m * 2) + +// flow :: Number -> Number +const flow = compose( + unbox, + chain(doubleBoxed), + map(add(10)), + Box +) + +flow(10) +//=> 40 +``` + +
+ +
+ +## Transformation Functions + +#### writerToPair + +`crocks/Pair/writerToPair` + +```haskell +writerToPair :: Monoid m => Writer m a -> Pair m a +writerToPair :: Monoid m => (a -> Writer m b) -> a -> Pair m b +``` + +Used to transform a `Writer` instance to a `Pair` instance, +`writerToPair` will take a given `Writer` and provide a new `Pair` with +the `log` portion of the `Writer` in the first position and the `resultant` +in the second. + +Like all `crocks` transformation functions, `writerToPair` has (2) possible +signatures and will behave differently when passed either a `Writer` instance +or a function that returns an instance of `Writer`. When passed the instance, +a `Pair` instance is returned. When passed a `Writer` returning function, +a function will be returned that takes a given value and returns an `Pair`. + +```javascript +import Pair from 'crocks/Pair' +import Sum from 'crocks/Sum' +import Writer from 'crocks/Writer' + +import writerToPair from 'crocks/Pair/writerToPair' + +// SumWriter :: Writer Sum a +const SumWriter = + Writer(Sum) + +// appendItem :: a -> [ a ] -> SumWriter [ a ] +const appendItem = item => xs => + SumWriter(1, xs.concat([ item ])) + +SumWriter(0, []) + .chain(appendItem('one')) + .chain(appendItem('two')) + .chain(appendItem('three')) +//=> Writer (Sum 3) [ "one", "two", "three" ] + +writerToPair(SumWriter(2, 'result')) +//=> Pair(Sum 2, 'result') + +Pair(Sum.empty(), []) + .chain(writerToPair(appendItem('one'))) + .chain(writerToPair(appendItem('two'))) + .chain(writerToPair(appendItem('three'))) +//=> Pair(Sum 3, [ "one", "two", "threimport +``` + +
+ +[fanout]: ../functions/helpers.html#fanout +[frompairs]: ../functions/helpers.html#frompairs +[identity]: ../functions/combinators.html#identity diff --git a/docs/src/pages/docs/crocks/index.md b/docs/src/pages/docs/crocks/index.md index 2f3752904..e832dd6e5 100644 --- a/docs/src/pages/docs/crocks/index.md +++ b/docs/src/pages/docs/crocks/index.md @@ -26,7 +26,7 @@ but what they do from type to type may vary. | `IO` | `of` | `ap`, `chain`, `map`, `of`, `run` | | `List` | `empty`, `fromArray`, `of` | `ap`, `chain`, `concat`, `cons`, `empty`, `equals`, `filter`, `head`, `map`, `of`, `reduce`, `reduceRight`, `reject`, `sequence`, `tail`, `toArray`, `traverse`, `valueOf` | | [`Maybe`][maybe] | [`Nothing`][maybe-nothing], [`Just`][maybe-just], [`of`][maybe-of], [`zero`][maybe-zero] | [`alt`][maybe-alt], [`ap`][maybe-ap], [`chain`][maybe-chain], [`coalesce`][maybe-coalesce], [`concat`][maybe-concat], [`equals`][maybe-equals], [`either`][maybe-either], [`map`][maybe-map], [`of`][maybe-of], [`option`][maybe-option], [`sequence`][maybe-sequence], [`traverse`][maybe-traverse], [`zero`][maybe-zero] | -| `Pair` | --- | `ap`, `bimap`, `chain`, `concat`, `equals`, `extend`, `fst`, `map`, `merge`, `of`, `sequence`, `snd`, `swap`, `traverse`, `toArray` | +| [`Pair`][pair] | --- | [`ap`][pair-ap], [`bimap`][pair-bimap], [`chain`][pair-chain], [`concat`][pair-concat], [`equals`][pair-equals], [`extend`][pair-extend], [`fst`][pair-fst], [`map`][pair-map], [`merge`][pair-merge], [`sequence`][pair-sequence], [`snd`][pair-snd], [`swap`][pair-swap], [`traverse`][pair-traverse], [`toArray`][pair-toarray] | | [`Pred`][pred] * | [`empty`][pred-empty] | [`concat`][pred-concat], [`contramap`][pred-contra], [`runWith`][pred-run], [`valueOf`][pred-value] | | [`Reader`][reader] | [`ask`][reader-ask], [`of`][reader-of] | [`ap`][reader-ap], [`chain`][reader-chain], [`map`][reader-map], [`runWith`][reader-run] | | [`ReaderT`][readert] | [`ask`][readert-ask], [`lift`][readert-lift], [`liftFn`][readert-liftfn], [`of`][readert-of] | [`ap`][readert-ap], [`chain`][readert-chain], [`map`][readert-map], [`runWith`][readert-run] | @@ -82,6 +82,22 @@ but what they do from type to type may vary. [equiv-compare]: Equiv.html#comparewith [equiv-value]: Equiv.html#valueof +[pair]: Pair.html +[pair-ap]: Pair.html#ap +[pair-bimap]: Pair.html#bimap +[pair-chain]: Pair.html#chain +[pair-concat]: Pair.html#concat +[pair-equals]: Pair.html#equals +[pair-extend]: Pair.html#extend +[pair-fst]: Pair.html#fst +[pair-map]: Pair.html#map +[pair-merge]: Pair.html#merge +[pair-sequence]: Pair.html#sequence +[pair-snd]: Pair.html#snd +[pair-swap]: Pair.html#swap +[pair-traverse]: Pair.html#traverse +[pair-toarray]: Pair.html#toarray + [pred]: Pred.html [pred-empty]: Pred.html#empty [pred-concat]: Pred.html#concat diff --git a/docs/src/pages/docs/functions/helpers.md b/docs/src/pages/docs/functions/helpers.md index 457a7bfbd..dbab0a0ef 100644 --- a/docs/src/pages/docs/functions/helpers.md +++ b/docs/src/pages/docs/functions/helpers.md @@ -57,23 +57,6 @@ if you need to constrain to more than (2) arguments, then you will want to reach for [`nAry`](#nary). `binary` is basically syntactic sugar for `nAry(2, fn)`. Also related is [`unary`](#unary), which constrains to (1) argument. -#### branch - -`crocks/Pair/branch` - -```haskell -branch :: a -> Pair a a -``` -When you want to branch a computation into two parts, this is the function you -want to reach for. All it does is let you pass in any `a` and will return you a -`Pair` that has your value on both the first and second parameter. This allows -you to work on the value in two separate computation paths. Be advised that this -is Javascript and if `a` is an object type (`Object`, `Array`, `Date`, etc) they -will reference each other. - -**Pro-Tip**: `Pair` provides a `merge` function that will let you fold the two -values into a single value. - #### compose `crocks/helpers/compose` @@ -336,7 +319,7 @@ to the first portion of the underlying `Pair` and the second on the second. fromPairs :: Foldable f => f (Pair String a) -> Object ``` -As an inverse to [`toPairs`](#topairs), `fromPairs` takes either an `Array` or +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 @@ -962,23 +945,6 @@ 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 - -`crocks/Pair/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 `crocks/Result/tryCatch` @@ -1024,3 +990,4 @@ represent some special value for a given type. This pointed use is the heart and soul of the infamous `Maybe` type. [safe]: ../crocks/Maybe.html#safe +[topairs]: ../crocks/Pair.html#topairs diff --git a/docs/src/pages/docs/functions/index.md b/docs/src/pages/docs/functions/index.md index bf1fb90d6..307312429 100644 --- a/docs/src/pages/docs/functions/index.md +++ b/docs/src/pages/docs/functions/index.md @@ -67,7 +67,7 @@ need to account for for the rest of your flow. | [`defaultTo`][defaultto] | `a -> b -> a` | `crocks/helpers/defaultTo` | | [`dissoc`][dissoc] | `String -> Object -> Object` | `crocks/helpers/dissoc` | | [`fanout`][fanout] | `(a -> b) -> (a -> c) -> (a -> Pair b c)` | `crocks/helpers/fanout` | -| [`find`][find] | `Foldable f => ((a -> Boolean) | Pred) -> f a -> Maybe a` | `crocks/helpers/find` | +| [`find`][find] | Foldable f => ((a -> Boolean) | Pred) -> f a -> Maybe a | `crocks/helpers/find` | | [`fromPairs`][frompairs] | `Foldable f => f (Pair String a) -> Object` | `crocks/helpers/fromPairs` | | [`liftA2`][lifta2] | `Applicative m => (a -> b -> c) -> m a -> m b -> m c` | `crocks/helpers/liftA2` | | [`liftA3`][lifta3] | `Applicative m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d` | `crocks/helpers/liftA3` | @@ -131,7 +131,7 @@ type: `Pred a` and vice-versa [assign]: helpers.html#assign [assoc]: helpers.html#assoc [binary]: helpers.html#binary -[branch]: helpers.html#branch +[branch]: ../crocks/Pair.html#branch [compose]: helpers.html#compose [composek]: helpers.html#composek [composep]: helpers.html#composep @@ -170,7 +170,7 @@ type: `Pred a` and vice-versa [safeafter]: ../crocks/Maybe.html#safeafter [safelift]: ../crocks/Maybe.html#safelift [tap]: helpers.html#tap -[topairs]: helpers.html#topairs +[topairs]: ../crocks/Pair.html#topairs [trycatch]: helpers.html#trycatch [unary]: helpers.html#unary [unit]: helpers.html#unit diff --git a/docs/src/pages/docs/functions/pointfree-functions.md b/docs/src/pages/docs/functions/pointfree-functions.md index d39886c45..608bb1adb 100644 --- a/docs/src/pages/docs/functions/pointfree-functions.md +++ b/docs/src/pages/docs/functions/pointfree-functions.md @@ -68,11 +68,11 @@ accepted Datatype): | `filter` | ((a -> Boolean) | Pred a) -> m a -> m a | `crocks/pointfree` | | `first` | `m (a -> b) -> m (Pair a c -> Pair b c)` | `crocks/pointfree` | | `fold` | `Semigroup s => m s -> s` | `crocks/pointfree` | -| `fst` | `m a b -> a` | `crocks/Pair` | +| [`fst`][fst] | `m a b -> a` | `crocks/Pair` | | `head` | `m a -> Maybe a` | `crocks/pointfree` | | `log` | `m a b -> a` | `crocks/Writer` | | `map` | `(a -> b) -> m a -> m b` | `crocks/pointfree` | -| `merge` | `(a -> b -> c) -> m a b -> c` | `crocks/Pair` | +| [`merge`][merge] | `(a -> b -> c) -> m a b -> c` | `crocks/Pair` | | `option` | `a -> m a -> a` | `crocks/pointfree` | | `promap` | `(c -> a) -> (b -> d) -> m a b -> m c d` | `crocks/pointfree` | | `read` | `m a b -> Pair a b` | `crocks/Writer` | @@ -83,7 +83,7 @@ accepted Datatype): | `runWith` | `a -> m -> b` | `crocks/pointfree` | | `second` | `m (a -> b) -> m (Pair c a -> Pair c b)` | `crocks/pointfree` | | `sequence` | Applicative TypeRep t, Apply f => (t | (b -> f b)) -> m (f a) -> f (m a) | `crocks/pointfree` | -| `snd` | `m a b -> b` | `crocks/Pair` | +| [`snd`][snd] | `m a b -> b` | `crocks/Pair` | | `swap` | `(c -> d) -> (a -> b) -> m c a -> m b d` | `crocks/pointfree` | | `tail` | `m a -> Maybe (m a)` | `crocks/pointfree` | | `traverse` | Applicative TypeRep t, Apply f => (t | (c -> f c)) -> (a -> f b) -> m (f a) -> f (m b) | `crocks/pointfree` | @@ -93,29 +93,29 @@ accepted Datatype): | Function | Datatypes | |---|:---| | `alt` | [`Async`][async-alt], `Either`, [`Maybe`][maybe-alt], `Result` | -| `ap` | `Array`, [`Async`][async-ap], [`Const`][const-ap], `Either`, `Identity`, `IO`, `List`, [`Maybe`][maybe-ap], `Pair`, [`Reader`][reader-ap], `Result`, [`State`][state-ap], `Unit`, `Writer` | -| `bimap` | [`Async`][async-bimap], `Either`, `Pair`, `Result` | +| `ap` | `Array`, [`Async`][async-ap], [`Const`][const-ap], `Either`, `Identity`, `IO`, `List`, [`Maybe`][maybe-ap], [`Pair`][pair-ap], [`Reader`][reader-ap], `Result`, [`State`][state-ap], `Unit`, `Writer` | +| `bimap` | [`Async`][async-bimap], `Either`, [`Pair`][pair-bimap], `Result` | | `both` | [`Arrow`][arrow-both], `Function`, `Star` | -| `chain` | `Array`, [`Async`][async-chain], [`Const`][const-chain], `Either`, `Identity`, `IO`, `List`, [`Maybe`][maybe-chain], `Pair`, [`Reader`][reader-chain], `Result`, [`State`][state-chain], `Unit`, `Writer` | +| `chain` | `Array`, [`Async`][async-chain], [`Const`][const-chain], `Either`, `Identity`, `IO`, `List`, [`Maybe`][maybe-chain], [`Pair`][pair-chain], [`Reader`][reader-chain], `Result`, [`State`][state-chain], `Unit`, `Writer` | | `coalesce` | [`Async`][async-coalesce], `Either`, [`Maybe`][maybe-coalesce], `Result` | | `compareWith` | [`Equiv`][equiv-compare] | -| `concat` | [`All`][all-concat], [`Any`][any-concat], `Array`, [`Assign`][assign-concat], [`Const`][const-concat], `Either`, [`Endo`][endo-concat], [`Equiv`][equiv-concat], [`First`][first-concat], `Identity`, [`Last`][last-concat], `List`, [`Max`][max-concat], [`Maybe`][maybe-concat], [`Min`][min-concat], `Pair`, [`Pred`][pred-concat], [`Prod`][prod-concat], `Result`, `String`, [`Sum`][sum-concat], `Unit` | +| `concat` | [`All`][all-concat], [`Any`][any-concat], `Array`, [`Assign`][assign-concat], [`Const`][const-concat], `Either`, [`Endo`][endo-concat], [`Equiv`][equiv-concat], [`First`][first-concat], `Identity`, [`Last`][last-concat], `List`, [`Max`][max-concat], [`Maybe`][maybe-concat], [`Min`][min-concat], [`Pair`][pair-concat], [`Pred`][pred-concat], [`Prod`][prod-concat], `Result`, `String`, [`Sum`][sum-concat], `Unit` | | `cons` | `Array`, `List` | | `contramap` | [`Arrow`][arrow-contra], [`Equiv`][equiv-contra], [`Pred`][pred-contra], `Star` | | `either` | `Either`, [`Maybe`][maybe-either], `Result` | | `empty` | [`All`][all-empty], [`Any`][any-empty], `Array`, [`Assign`][assign-empty], [`Endo`][endo-empty], [`Equiv`][equiv-empty], [`First`][first-empty], [`Last`][last-empty], `List`, [`Max`][max-empty], [`Min`][min-empty], `Object`, [`Pred`][pred-empty], [`Prod`][prod-empty], `String`, [`Sum`][sum-empty], `Unit` | -| `equals` | [`All`][all-equals], [`Any`][any-equals], `Array`, [`Assign`][assign-equals], `Boolean`, [`Const`][const-equals], `Either`, [`First`][first-equals], [`Last`][last-equals], `List`, [`Max`][max-equals], [`Maybe`][maybe-equals], [`Min`][min-equals], `Number`, `Object`, `Pair`, [`Prod`][prod-equals], `Result`, `String`, [`Sum`][sum-equals], `Unit`, `Writer` | +| `equals` | [`All`][all-equals], [`Any`][any-equals], `Array`, [`Assign`][assign-equals], `Boolean`, [`Const`][const-equals], `Either`, [`First`][first-equals], [`Last`][last-equals], `List`, [`Max`][max-equals], [`Maybe`][maybe-equals], [`Min`][min-equals], `Number`, `Object`, [`Pair`][pair-equals], [`Prod`][prod-equals], `Result`, `String`, [`Sum`][sum-equals], `Unit`, `Writer` | | [`evalWith`][eval] | [`State`][state-eval] | | [`execWith`][exec] | [`State`][state-exec] | -| `extend` | `Pair` | +| `extend` | [`Pair`][pair-extend] | | `filter` | `Array`, `List`, `Object` | | `first` | [`Arrow`][arrow-first], `Function`, `Star` | | `fold` | `Array`, `List` | -| `fst` | `Pair` | +| [`fst`][fst] | [`Pair`][pair-fst] | | `head` | `Array`, `List`, `String` | | `log` | `Writer` | -| `map` | [`Async`][async-map], `Array`, [`Arrow`][arrow-map], [`Const`][const-map], `Either`, `Function`, `Identity`, `IO`, `List`, [`Maybe`][maybe-map], `Object`, `Pair`, [`Reader`][reader-map], `Result`, `Star`, [`State`][state-map], `Unit`, `Writer` | -| `merge` | `Pair` | +| `map` | [`Async`][async-map], `Array`, [`Arrow`][arrow-map], [`Const`][const-map], `Either`, `Function`, `Identity`, `IO`, `List`, [`Maybe`][maybe-map], `Object`, [`Pair`][pair-map], [`Reader`][reader-map], `Result`, `Star`, [`State`][state-map], `Unit`, `Writer` | +| [`merge`][merge] | [`Pair`][pair-merge] | | `option` | [`First`][first-option], [`Last`][last-option], [`Maybe`][maybe-option] | | `promap` | [`Arrow`][arrow-pro], `Star` | | `read` | `Writer` | @@ -125,11 +125,11 @@ accepted Datatype): | `run` | `IO` | | `runWith` | [`Arrow`][arrow-run], [`Endo`][endo-run], [`Pred`][pred-run], [`Reader`][reader-run], `Star`, [`State`][state-run] | | `second` | [`Arrow`][arrow-second], `Function`, `Star` | -| `sequence` | `Array`, `Either`, `Identity`, `List`, [`Maybe`][maybe-sequence], `Pair`, `Result` | -| `snd` | `Pair` | -| `swap` | [`Async`][async-swap], `Either`, `Pair`, `Result` | +| `sequence` | `Array`, `Either`, `Identity`, `List`, [`Maybe`][maybe-sequence], [`Pair`][pair-sequence], `Result` | +| [`snd`][snd] | [`Pair`][pair-snd] | +| `swap` | [`Async`][async-swap], `Either`, [`Pair`][pair-swap], `Result` | | `tail` | `Array`, `List`, `String` | -| `traverse` | `Array`, `Either`, `Identity`, `List`, [`Maybe`][maybe-traverse], `Pair`, `Result` | +| `traverse` | `Array`, `Either`, `Identity`, `List`, [`Maybe`][maybe-traverse], [`Pair`][pair-traverse], `Result` | | `valueOf` | [`All`][all-value], [`Any`][any-value], [`Assign`][assign-value], [`Const`][const-value], [`Endo`][endo-value], [`Equiv`][equiv-value], [`First`][first-value], `Identity`, [`Last`][last-value], [`Max`][max-value], [`Min`][min-value], [`Pred`][pred-value], [`Prod`][prod-value], [`Sum`][sum-value], `Unit`, `Writer` | [all-concat]: ../monoids/All.html#concat @@ -215,6 +215,20 @@ accepted Datatype): [min-equals]: ../monoids/Min.html#equals [min-value]: ../monoids/Min.html#valueof +[pair-ap]: ../crocks/Pair.html#ap +[pair-bimap]: ../crocks/Pair.html#bimap +[pair-concat]: ../crocks/Pair.html#concat +[pair-chain]: ../crocks/Pair.html#chain +[pair-equals]: ../crocks/Pair.html#equals +[pair-extend]: ../crocks/Pair.html#extend +[pair-fst]: ../crocks/Pair.html#fst +[pair-map]: ../crocks/Pair.html#map +[pair-merge]: ../crocks/Pair.html#merge +[pair-sequence]: ../crocks/Pair.html#sequence +[pair-snd]: ../crocks/Pair.html#snd +[pair-swap]: ../crocks/Pair.html#swap +[pair-traverse]: ../crocks/Pair.html#traverse + [pred-concat]: ../crocks/Pred.html#concat [pred-contra]: ../crocks/Pred.html#contramap [pred-empty]: ../crocks/Pred.html#empty @@ -245,3 +259,7 @@ accepted Datatype): [exec]: ../crocks/State.html#execwith-pointfree [eval]: ../crocks/State.html#evalwith-pointfree + +[fst]: ../crocks/Pair.html#fst-pointfree +[merge]: ../crocks/Pair.html#merge-pointfree +[snd]: ../crocks/Pair.html#snd-pointfree