From 9252f2e966a729e30eb35acdfb1a3f66d12cf0c8 Mon Sep 17 00:00:00 2001 From: "Ian Hofmann-Hicks (evil)" Date: Tue, 23 Jan 2018 10:32:04 -0800 Subject: [PATCH 1/3] add first-docs --- src/First/README.md | 451 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 src/First/README.md diff --git a/src/First/README.md b/src/First/README.md new file mode 100644 index 000000000..87b3c8b7c --- /dev/null +++ b/src/First/README.md @@ -0,0 +1,451 @@ +# First + +```haskell +First a = First (Maybe a) +``` + +`First` is a `Monoid` that will always return the first, non-empty value when +(2) `First` instances are combined. `First` is able to be a `Monoid` because +it implements a `Maybe` under the hood. The use of the `Maybe` allows for an +empty to be represented with a `Nothing`. + +`First` can be constructed with either a value or a `Maybe` instance. Any value +passed to the constructor will be wrapped in a `Just` to represent a non-empty +instance of `First`. Any `Maybe` passed to the constructor will be lifted as +is, allowing the ability to "choose" an value based on some disjunction. + +While most `Monoid`s only provide a [`valueOf`](#valueof) function used for +extraction, `Last` takes advantage of its underlying `Maybe` to provide an +additional [`option`](#option) method. Using [`valueOf`](#valueof) will extract +the underlying `Maybe`, while [`option`](#option) will extract the underlying +value in the `Maybe`, using the provided default value when the underlying +`Maybe` is a `Nothing` instance. + +```javascript +const First = require('crocks/First') + +const and = require('crocks/logic/and') +const isNumber = require('crocks/predicates/isNumber') +const mconcatMap = require('crocks/helpers/mconcatMap') +const safe = require('crocks/Maybe/safe') + +// isEven :: Number -> Boolean +const isEven = + x => !(x % 2) + +// isValid :: a -> Boolean +const isValid = + and(isNumber, isEven) + +// chooseFirst :: [ * ] -> First Number +const chooseFirst = + mconcatMap(First, safe(isValid)) + +chooseFirst([ 21, 45, 2, 22, 19 ]) + .valueOf(0) +//=> Just 2 + +chooseFirst([ 'a', 'b', 'c' ]) + .option('') +//=> "" +``` + +## Implements + +`Semigroup`, `Monoid` + +## Constructor Methods + +#### empty + +```haskell +First.empty :: () -> First a +``` + +`empty` provides the identity for the `Monoid` in that when the value it +provides is `concat`ed to any other value, it will return the other value. In +the case of `First` the result of `empty` is `Nothing`. `empty` is available +on both the Constructor and the Instance for convenience. + +```javascript +const First = require('crocks/First') +const { empty } = First + +First.empty() +//=> First( Nothing ) + +First(3) + .concat(empty()) +//=> First( Just 3 ) + +empty() + .concat(First(3)) +//=> First( Just 3 ) +``` + +## Instance Methods + +#### concat + +```haskell +First a ~> First a -> First a +``` + +`concat` is used to combine (2) `Semigroup`s of the same type under an operation +specified by the `Semigroup`. In the case of `First`, it will always provide the +first non-empty value. Any subsequent non-empty values will be thrown away, and +will always result in the first non-empty value. + +```javascript +const First = require('crocks/First') +const concat = require('crocks/pointfree/concat') + +const a = First('a') +const b = First('b') +const c = First('c') + +a.concat(b) +//=> First( Just "a" ) + +b.concat(a) +//=> First( Just "b" ) + +concat(c, concat(b, a)) +//=> First( Just "a" ) + +concat(concat(c, b), a) +//=> First( Just "a" ) + +concat(concat(a, b), c) +//=> First( Just "c" ) +``` + +#### option + +```haskell +First a ~> a -> a +``` + +`First` wraps an underlying `Maybe` which provides the ability to option out +a value in the case as of an empty instance. Just like `option` on a `Maybe` +instance, it takes a value as its argument. When run on an empty instance, +the provided default will be returned. If `option` is run on a non-empty +instance however, the wrapped value will be extracted not only from the +`Last`, but also from the underlying `Just`. + +If the underlying `Maybe` is desired, the [`valueOf`](#valueof) method can be +used and will return the `Maybe` instead. + +```javascript +const First = require('crocks/First') + +const compose = require('crocks/helpers/compose') +const chain = require('crocks/pointfree/chain') +const isString = require('crocks/predicates/isString') +const mconcatMap = require('crocks/helpers/mconcatMap') +const prop = require('crocks/Maybe/prop') +const safe = require('crocks/Maybe/safe') + +// stringVal :: a -> Maybe String +const stringVal = compose( + chain(safe(isString)), + prop('val') +) + +// firstValid :: [ a ] -> First String +const firstValid = + mconcatMap(First, stringVal) + +// good :: [ Object ] +const good = + [ { val: 23 }, { val: 'string' }, { val: '23' } ] + +// bad :: [ Object ] +const bad = + [ { val: 23 }, { val: null }, {} ] + +firstValid(good) + .option('') +//=> "string" + +firstValid(bad) + .option('') +//=> "" +``` + +#### valueOf + +```haskell +First a ~> () -> Maybe a +``` + +`valueOf` is used on all `crocks` `Monoid`s as a means of extraction. While the +extraction is available, types that implement `valueOf` are not necessarily a +`Comonad`. This function is used primarily for convenience for some of the +helper functions that ship with `crocks`. Calling `valueOf` on +a `First` instance will result in the underlying `Maybe`. + +```javascript +const First = require('crocks/First') + +const { Nothing } = require('crocks/Maybe') +const valueOf = require('crocks/pointfree/valueOf') + +valueOf(First(56)) +//=> Just 56 + +valueOf(First.empty()) +//=> Nothing + +First(37) + .concat(First(99)) + .valueOf() +//=> Just 37 + +First(Nothing()) + .concat(First.empty()) + .valueOf() +//=> Nothing +``` + +## Transformation Functions + +#### eitherToFirst + +`crocks/First/eitherToFirst` + +```haskell +eitherToFirst :: Either b a -> First a +eitherToFirst :: (a -> Either c b) -> a -> First b +``` + +Used to transform a given `Either` instance to a `First` +instance, `eitherToFirst` will turn a `Right` instance into a non-empty `First` +wrapping the original value contained in the `Right`. All `Left` instances will +map to an empty `First`, mapping the originally contained value to a `Unit`. +Values on the `Left` will be lost and as such this transformation is considered +lossy in that regard. + +Like all `crocks` transformation functions, `eitherToFirst` has (2) possible +signatures and will behave differently when passed either an `Either` instance +or a function that returns an instance of `Either`. When passed the instance, +a transformed `First` is returned. When passed an `Either` returning function, +a function will be returned that takes a given value and returns a `First`. + +```javascript +const First = require('crocks/First') +const { Left, Right } = require('crocks/Either') +const eitherToFirst = require('crocks/First/eitherToFirst') + +const concat = require('crocks/pointfree/concat') +const constant = require('crocks/combinators/constant') +const flip = require('crocks/combinators/flip') +const ifElse = require('crocks/logic/ifElse') +const isNumber = require('crocks/predicates/isNumber') +const mapReduce = require('crocks/helpers/mapReduce') + +// someNumber :: a -> Either String Number +const someNumber = ifElse( + isNumber, + Right, + constant(Left('Nope')) +) + +// firstNumber :: [ a ] -> First Number +const firstNumber = mapReduce( + eitherToFirst(someNumber), + flip(concat), + First.empty() +) + +eitherToFirst(Left('Bad Times')) +//=> First( Nothing ) + +eitherToFirst(Right('correct')) +//=> First( Just "correct" ) + +firstNumber([ 'string', null, 34, 76 ]) +//=> First( Just 34 ) + +firstNumber([ 'string', null, true ]) +//=> First( Nothing ) +``` + +#### lastToFirst + +`crocks/First/lastToFirst` + +```haskell +lastToFirst :: Last a -> First a +lastToFirst :: (a -> Last b) -> a -> First b +``` + +Used to transform a given `Last` instance to a `First` instance, `lastToFirst` +will turn a non-empty instance into a non-empty `First` wrapping the original +value contained within the `Last`. All empty instances will map to an empty +`First`. + +Like all `crocks` transformation functions, `lastToFirst` has (2) possible +signatures and will behave differently when passed either a `Last` instance +or a function that returns an instance of `Last`. When passed the instance, +a transformed `First` is returned. When passed a `Last` returning function, +a function will be returned that takes a given value and returns a `First`. + +```javascript +const First = require('crocks/First') +const Last = require('crocks/Last') +const lastToFirst = require('crocks/First/lastToFirst') + +const isString = require('crocks/predicates/isString') +const mconcatMap = require('crocks/helpers/mconcatMap') +const safe = require('crocks/Maybe/safe') + +// lastString :: [ a ] -> Last String +const lastString = + mconcatMap(Last, safe(isString)) + +// fixLastString :: [ a ] -> First String +const fixLastString = + lastToFirst(lastString) + +lastToFirst(Last.empty()) +//=> First( Nothing ) + +lastToFirst(Last(false)) +//=> First( Just false ) + +fixLastString([ 'one', 2, 'Three', 4 ]) + .concat(First('another string')) +//=> First( Just "Three" ) + +fixLastString([ 1, 2, 3, 4 ]) + .concat(First('First String')) +//=> First( Just "First String" ) +``` + +#### maybeToFirst + +`crocks/First/maybeToFirst` + +```haskell +maybeToFirst :: Maybe a -> First a +maybeToFirst :: (a -> Maybe b) -> a -> First b +``` + +Used to transform a given `Maybe` instance to a `First` +instance, `maybeToFirst` will turn a `Just` into a non-empty `First` instance, +wrapping the original value contained within the `First`. +All `Nothing` instances will map to an empty `First` instance. + +This function is available mostly for completion sake, as `First` can always +take a `Maybe` as its argument during construction. So while there is not a +real need for this to be used for transforming instances, it can come in +handy for lifting `Maybe` returning functions. + +Like all `crocks` transformation functions, `maybeToFirst` has (2) possible +signatures and will behave differently when passed either a `Maybe` instance +or a function that returns an instance of `Maybe`. When passed the instance, +a transformed `First` is returned. When passed a `Maybe` returning function, +a function will be returned that takes a given value and returns a `First`. + +```javascript +const First = require('crocks/First') +const { Nothing, Just } = require('crocks/Maybe') +const maybeToFirst = require('crocks/First/maybeToFirst') + +const chain = require('crocks/pointfree/chain') +const compose = require('crocks/helpers/compose') +const isNumber = require('crocks/predicates/isNumber') +const prop = require('crocks/Maybe/prop') +const safe = require('crocks/Maybe/safe') + +// numVal :: a -> Maybe Number +const numVal = compose( + chain(safe(isNumber)), + prop('val') +) + +// numVal :: a -> First Number +const firstNumVal = + maybeToFirst(numVal) + +maybeToFirst(Just(99)) +//=> First( Just 99 ) + +maybeToFirst(Nothing()) +//=> First( Nothing ) + +First(Just(99)) +//=> First( Just 99 ) + +First(Nothing()) +//=> First( Nothing ) + +firstNumVal({ val: 97 }) + .concat(First(80)) +//=> First( Just 97 ) + +firstNumVal({ val: '97' }) + .concat(First(80)) +//=> First( Just 80 ) + +firstNumVal(null) + .concat(First(80)) +//=> First( Just 80 ) +``` + +#### resultToFirst + +`crocks/First/resultToFirst` + +```haskell +resultToFirst :: Result e a -> First a +resultToFirst :: (a -> Result e b) -> a -> First b +``` + +Used to transform a given `Result` instance to a `First` instance, +`resultToFirst` will turn an `Ok` instance into a non-empty `First`, +wrapping the original value contained in the `Ok`. All `Err` instances will map +to an empty `First`, mapping the originally contained value to a `Unit`. Values +on the `Err` will be lost and as such this transformation is considered lossy in +that regard. + +Like all `crocks` transformation functions, `resultToFirst` has (2) possible +signatures and will behave differently when passed either an `Result` instance +or a function that returns an instance of `Result`. When passed the instance, +a transformed `First` is returned. When passed a `Result` returning function, +a function will be returned that takes a given value and returns a `First`. + +```javascript +const First = require('crocks/First') +const { Err, Ok } = require('crocks/Result') +const resultToFirst = require('crocks/First/resultToFirst') + +const isNumber = require('crocks/predicates/isNumber') +const tryCatch = require('crocks/Result/tryCatch') + +function onlyNums(x) { + if(!isNumber(x)) { + throw new Error('something amiss') + } + return x +} + +// firstNum :: a -> First Number +const firstNum = + resultToFirst(tryCatch(onlyNums)) + +resultToFirst(Err('this is bad')) +//=> Nothing + +resultToFirst(Ok('this is great')) +//=> Just "this is great" + +firstNum(90) + .concat(First(0)) +//=> First( Just 90 ) + +firstNum(null) + .concat(First(0)) +//=> First( Just 0 ) +``` From 216125e15e1f7ebb1719a4d4d8faa9e3da47a2dd Mon Sep 17 00:00:00 2001 From: Ian Hofmann-Hicks Date: Mon, 29 Jan 2018 20:34:35 -0800 Subject: [PATCH 2/3] some initial PR feedback --- src/First/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/First/README.md b/src/First/README.md index 87b3c8b7c..cb3e59cfe 100644 --- a/src/First/README.md +++ b/src/First/README.md @@ -12,7 +12,7 @@ empty to be represented with a `Nothing`. `First` can be constructed with either a value or a `Maybe` instance. Any value passed to the constructor will be wrapped in a `Just` to represent a non-empty instance of `First`. Any `Maybe` passed to the constructor will be lifted as -is, allowing the ability to "choose" an value based on some disjunction. +is, allowing the ability to "choose" a value based on some disjunction. While most `Monoid`s only provide a [`valueOf`](#valueof) function used for extraction, `Last` takes advantage of its underlying `Maybe` to provide an @@ -42,7 +42,7 @@ const chooseFirst = mconcatMap(First, safe(isValid)) chooseFirst([ 21, 45, 2, 22, 19 ]) - .valueOf(0) + .valueOf() //=> Just 2 chooseFirst([ 'a', 'b', 'c' ]) @@ -93,7 +93,7 @@ First a ~> First a -> First a `concat` is used to combine (2) `Semigroup`s of the same type under an operation specified by the `Semigroup`. In the case of `First`, it will always provide the -first non-empty value. Any subsequent non-empty values will be thrown away, and +first non-empty value. Any subsequent non-empty values will be thrown away and will always result in the first non-empty value. ```javascript @@ -127,11 +127,11 @@ First a ~> a -> a ``` `First` wraps an underlying `Maybe` which provides the ability to option out -a value in the case as of an empty instance. Just like `option` on a `Maybe` +a value in the case of an empty instance. Just like `option` on a `Maybe` instance, it takes a value as its argument. When run on an empty instance, the provided default will be returned. If `option` is run on a non-empty instance however, the wrapped value will be extracted not only from the -`Last`, but also from the underlying `Just`. +`Last` but also from the underlying `Just`. If the underlying `Maybe` is desired, the [`valueOf`](#valueof) method can be used and will return the `Maybe` instead. @@ -220,7 +220,7 @@ eitherToFirst :: (a -> Either c b) -> a -> First b ``` Used to transform a given `Either` instance to a `First` -instance, `eitherToFirst` will turn a `Right` instance into a non-empty `First` +instance, `eitherToFirst` will turn a `Right` instance into a non-empty `First`, wrapping the original value contained in the `Right`. All `Left` instances will map to an empty `First`, mapping the originally contained value to a `Unit`. Values on the `Left` will be lost and as such this transformation is considered From 42c83ce18252c20c953a1860eff09afb25651f89 Mon Sep 17 00:00:00 2001 From: Ian Hofmann-Hicks Date: Tue, 30 Jan 2018 13:20:52 -0800 Subject: [PATCH 3/3] additional updates from First feedback --- src/First/README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/First/README.md b/src/First/README.md index cb3e59cfe..0ed4d96fb 100644 --- a/src/First/README.md +++ b/src/First/README.md @@ -7,7 +7,7 @@ First a = First (Maybe a) `First` is a `Monoid` that will always return the first, non-empty value when (2) `First` instances are combined. `First` is able to be a `Monoid` because it implements a `Maybe` under the hood. The use of the `Maybe` allows for an -empty to be represented with a `Nothing`. +[`empty`][#empty] `First` to be represented with a `Nothing`. `First` can be constructed with either a value or a `Maybe` instance. Any value passed to the constructor will be wrapped in a `Just` to represent a non-empty @@ -127,11 +127,11 @@ First a ~> a -> a ``` `First` wraps an underlying `Maybe` which provides the ability to option out -a value in the case of an empty instance. Just like `option` on a `Maybe` -instance, it takes a value as its argument. When run on an empty instance, -the provided default will be returned. If `option` is run on a non-empty -instance however, the wrapped value will be extracted not only from the -`Last` but also from the underlying `Just`. +a value in the case of an [`empty`](#empty) instance. Just like `option` on +a `Maybe` instance, it takes a value as its argument. When run on +an [`empty`](#empty) instance, the provided default will be returned. +If `option` is run on a non-empty instance however, the wrapped value will be +extracted not only from the `Last` but also from the underlying `Just`. If the underlying `Maybe` is desired, the [`valueOf`](#valueof) method can be used and will return the `Maybe` instead. @@ -222,7 +222,7 @@ eitherToFirst :: (a -> Either c b) -> a -> First b Used to transform a given `Either` instance to a `First` instance, `eitherToFirst` will turn a `Right` instance into a non-empty `First`, wrapping the original value contained in the `Right`. All `Left` instances will -map to an empty `First`, mapping the originally contained value to a `Unit`. +map to an [`empty`](#empty) `First`, mapping the originally contained value to a `Unit`. Values on the `Left` will be lost and as such this transformation is considered lossy in that regard. @@ -258,6 +258,7 @@ const firstNumber = mapReduce( First.empty() ) +// "Bad Times" is lost, mapped to Nothing eitherToFirst(Left('Bad Times')) //=> First( Nothing ) @@ -282,8 +283,8 @@ lastToFirst :: (a -> Last b) -> a -> First b Used to transform a given `Last` instance to a `First` instance, `lastToFirst` will turn a non-empty instance into a non-empty `First` wrapping the original -value contained within the `Last`. All empty instances will map to an empty -`First`. +value contained within the `Last`. All [`empty`](#empty) instances will map +to an [`empty`](#empty) `First`. Like all `crocks` transformation functions, `lastToFirst` has (2) possible signatures and will behave differently when passed either a `Last` instance @@ -335,7 +336,7 @@ maybeToFirst :: (a -> Maybe b) -> a -> First b Used to transform a given `Maybe` instance to a `First` instance, `maybeToFirst` will turn a `Just` into a non-empty `First` instance, wrapping the original value contained within the `First`. -All `Nothing` instances will map to an empty `First` instance. +All `Nothing` instances will map to an [`empty`](#empty) `First` instance. This function is available mostly for completion sake, as `First` can always take a `Maybe` as its argument during construction. So while there is not a @@ -406,9 +407,9 @@ resultToFirst :: (a -> Result e b) -> a -> First b Used to transform a given `Result` instance to a `First` instance, `resultToFirst` will turn an `Ok` instance into a non-empty `First`, wrapping the original value contained in the `Ok`. All `Err` instances will map -to an empty `First`, mapping the originally contained value to a `Unit`. Values -on the `Err` will be lost and as such this transformation is considered lossy in -that regard. +to an [`empty`](#empty) `First`, mapping the originally contained value to +a `Unit`. Values on the `Err` will be lost and as such this transformation is +considered lossy in that regard. Like all `crocks` transformation functions, `resultToFirst` has (2) possible signatures and will behave differently when passed either an `Result` instance @@ -435,6 +436,7 @@ function onlyNums(x) { const firstNum = resultToFirst(tryCatch(onlyNums)) +// "this is bad" is lost, mapped to Nothing resultToFirst(Err('this is bad')) //=> Nothing