Crocks

  • Documentation
Docs Menu
  • Getting Started
  • Crocks
    • Arrow
    • Async
    • Const
    • Either
    • Equiv
    • Identity
    • Maybe
    • Pair
    • Pred
    • Reader
    • ReaderT
    • Result
    • State
    • Tuple
  • Monoids
    • All
    • Any
    • Assign
    • Endo
    • First
    • Last
    • Max
    • Min
    • Prod
    • Sum
  • Functions
    • Combinators
    • Helpers
    • Logic Functions
    • Predicate Functions
    • Point-free Functions
    • Transformation Functions

Pair

Pair a b

Pair allows the ability to represent two 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 two 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 Pairs must be Semigroup instances of the same type. When applied, ap and chain will concatenate the Semigroups 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, merge andfanout. Using those helpers in conjunction with the ability tobimap functions over a given Pairs values.

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/pointfree/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

Construction

Pair :: (a, b) -> Pair a b

In order to construct a Pair, two values of any type are required by the constructor. The types of the arguments can, and often do, vary. None of the constructors in crocks are curried by default, so both arguments must be provided at the same time in order to construct the Pair.

Once both arguments are provided, the constructor will return a new Pair instance with the first argument in the left portion and the second argument in the right position.

import Pair from 'crocks/Pair'

Pair(34, false)
//=> Pair Number Boolean

Pair(34, Pair(true, 'string'))
//=> Pair Number (Pair Boolean String)

Instance Methods

equals

Pair a b ~> c -> Boolean

Used to compare the underlying values of two 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.

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

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 Semigroups of the same type and structure. Expecting a Maybe wrapping a Semigroup of the same type, concat will give back a new Pair instancewrapping the result of combining the underlying Semigroup instances.

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/Pair/fanout'
import flip from 'crocks/combinators/flip'
import getProp from 'crocks/Maybe/getProp'
import map from 'crocks/pointfree/map'
import mapReduce from 'crocks/helpers/mapReduce'
import option from 'crocks/pointfree/option'

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), getProp(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

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 can be used instead.

import Pair from 'crocks/Pair'

import compose from 'crocks/helpers/compose'
import map from 'crocks/pointfree/map'
import merge from 'crocks/pointfree/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

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 can be used to apply these transformations, bimap allows for independent transformations on both sides, in parallel.

bimap takes two 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. If the need arises to map the first portion exclusively, use bimap passing the mapping function to the first argument and an identity to the second.

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/pointfree/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

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.

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

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 anadditional requirement, is that instances of the same Semigroup must occupy the first position of the source Pair and the Pair returned by the function.

const Pair = require('crocks/Pair')

const setProp = require('crocks/helpers/setProp')
const omit = require('crocks/helpers/omit')

// addTmp :: (String, a, Object) -> Pair [ String ] Object
const addTmp = (key, value, x) =>
  Pair([ key ], setProp(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 setProp('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

Apply f => Pair a (f b) ~> (c -> f c) -> f (Pair a b)
Applicative f => Pair a (f b) ~> TypeRep f -> 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 by passing it anidentity function (x => x).

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

Apply f => Pair a b ~> ((d -> f d), (b -> f c)) -> f (Pair a c)
Applicative f => Pair a b ~> (TypeRep f, (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 Applys "effect" on the value in the second position of the Pair.

The traverse method 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.

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

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.

import Pair from 'crocks/Pair'

import extend from 'crocks/pointfree/extend'
import merge from 'crocks/pointfree/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

Pair a b ~> ((a -> c), (b -> d)) -> Pair d c

Used to map the value of a Pairs first position into the second position and the second position into the first, swap takes two 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 functions can be used in one or both arguments.

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

Pair a b ~> () -> a

fst is one of two 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 is the other projection function provided and is used to extract the value in the second position.

import Pair from 'crocks/Pair'

Pair('left', 'right')
  .fst()
//=> "left"

snd

Pair a b ~> () -> b

snd is one of two 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 is the other projection function provided and is used to extract the value in the first position.

import Pair from 'crocks/Pair'

Pair('left', 'right')
  .snd()
//=> "right"

toArray

Pair a b ~> () -> [ a, b ]

While both fst and 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.

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

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 or [fanout][fanout] helper functions.

import Sum from 'crocks/Sum'

import compose from 'crocks/helpers/compose'
import fanout from 'crocks/Pair/fanout'
import merge from 'crocks/pointfree/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

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.

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/pointfree/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 }

fanout

crocks/Pair/fanout

fanout :: (a -> b) -> (a -> c) -> (a -> Pair b c)
fanout :: Arrow a b -> Arrow a c -> Arrow a (Pair b c)
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.

import compose from 'crocks/helpers/compose'
import fanout from 'crocks/Pair/fanout'
import getProp from 'crocks/Maybe/getProp'
import liftA2 from 'crocks/helpers/liftA2'
import map from 'crocks/pointfree/map'
import Maybe from 'crocks/Maybe'
import merge from 'crocks/Pair/merge'
import sequence from 'crocks/pointfree/sequence'

// Person :: { first: String, last: String }
// people :: [Person]
const people = [
  { first: 'Ziggy', last: 'Stardust' },
  { first: 'Lizard', last: 'King' }
]

// concat :: String -> String -> String
const concat = a => b => `${a} ${b}`

// join :: String -> [a] -> String
const join = sep => arr => arr.join(sep)

// getName :: Person -> String
const getName = compose(
  merge(liftA2(concat)),
  fanout(getProp('first'), getProp('last'))
)

// getPersons :: [Person] -> String
const getPersons = compose(
  map(join(', ')),
  sequence(Maybe),
  map(getName)
)

getPersons(people)
//=> Just "Ziggy Stardust, Lizard King"

toPairs

crocks/Pair/toPairs

toPairs :: Object -> List (Pair String a)

When dealing with Objects, sometimes it makes more sense to work in a Foldable structure like a List of key-value Pairs. 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.

import compose from 'crocks/helpers/compose'
import map from 'crocks/pointfree/map'
import merge from 'crocks/pointfree/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

fst :: Pair a b -> a

The fst pointfree function is used extract the leftmost value of a Pair by invoking the 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.

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/pointfree/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

snd (pointfree)

crocks/Pair/snd

snd :: Pair a b -> b

The snd pointfree function is used extract the rightmost value of a Pair by invoking the 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.

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

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 or flatten a Pair of Writer into an Pair when chained, 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 two 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.

import Pair from 'crocks/Pair'
import Sum from 'crocks/Sum'
import Writer from 'crocks/Writer'
import fanout from 'crocks/helpers/fanout'

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", "three"])

fanout(Sum, x => appendItem(x)([ x ]), 1)
  .chain(writerToPair)

Contribute on Github! Edit this section.