ūüôą Refuge from unsafe JavaScript
JavaScript Other
Latest commit 62877fc Jan 14, 2017 @davidchambers davidchambers committed on GitHub Merge pull request #324 from sanctuary-js/dc-version-urls
documentation: use version-specific project URLs

README.md

Sanctuary

npm CircleCI Gitter

Sanctuary is a functional programming library inspired by Haskell and PureScript. It depends on and works nicely with Ramda. Sanctuary makes it possible to write safe code without null checks.

In JavaScript it's trivial to introduce a possible run-time type error:

words[0].toUpperCase()

If words is [] we'll get a familiar error at run-time:

TypeError: Cannot read property 'toUpperCase' of undefined

Sanctuary gives us a fighting chance of avoiding such errors. We might write:

R.map(S.toUpper, S.head(words))

Sanctuary is designed to work in Node.js and in ES5-compatible browsers.

Types

Sanctuary uses Haskell-like type signatures to describe the types of values, including functions. 'foo', for example, has type String; [1, 2, 3] has type Array Number. The arrow (->) is used to express a function's type. Math.abs, for example, has type Number -> Number. That is, it takes an argument of type Number and returns a value of type Number.

R.map has type (a -> b) -> Array a -> Array b. That is, it takes an argument of type a -> b and returns a value of type Array a -> Array b. a and b are type variables: applying R.map to a value of type String -> Number will result in a value of type Array String -> Array Number.

Sanctuary embraces types. JavaScript doesn't support algebraic data types, but these can be simulated by providing a group of data constructors which return values with the same set of methods. A value of the Maybe type, for example, is created via the Nothing constructor or the Just constructor.

It's necessary to extend Haskell's notation to describe implicit arguments to the methods provided by Sanctuary's types. In x.map(y), for example, the map method takes an implicit argument x in addition to the explicit argument y. The type of the value upon which a method is invoked appears at the beginning of the signature, separated from the arguments and return value by a squiggly arrow (~>). The type of the map method of the Maybe type is written Maybe a ~> (a -> b) -> Maybe b. One could read this as:

When the map method is invoked on a value of type Maybe a (for any type a) with an argument of type a -> b (for any type b), it returns a value of type Maybe b.

Sanctuary supports type classes: constraints on type variables. Whereas a -> a implicitly supports every type, Functor f => (a -> b) -> f a -> f b requires that f be a type which satisfies the requirements of the Functor type class. Type-class constraints appear at the beginning of a type signature, separated from the rest of the signature by a fat arrow (=>).

Accessible pseudotype

What is the type of values which support property access? In other words, what is the type of which every value except null and undefined is a member? Object is close, but Object.create(null) produces a value which supports property access but which is not a member of the Object type.

Sanctuary uses the Accessible pseudotype to represent the set of values which support property access.

Integer pseudotype

The Integer pseudotype represents integers in the range (-2^53 .. 2^53). It is a pseudotype because each Integer is represented by a Number value. Sanctuary's run-time type checking asserts that a valid Number value is provided wherever an Integer value is required.

Type representatives

What is the type of Number? One answer is a -> Number, since it's a function which takes an argument of any type and returns a Number value. When provided as the first argument to is, though, Number is really the value-level representative of the Number type.

Sanctuary uses the TypeRep pseudotype to describe type representatives. For example:

Number :: TypeRep Number

Number is the sole inhabitant of the TypeRep Number type.

Type checking

Sanctuary functions are defined via sanctuary-def to provide run-time type checking. This is tremendously useful during development: type errors are reported immediately, avoiding circuitous stack traces (at best) and silent failures due to type coercion (at worst). For example:

S.inc('XXX');
// ! TypeError: Invalid value
//
//   inc :: FiniteNumber -> FiniteNumber
//          ^^^^^^^^^^^^
//               1
//
//   1)  "XXX" :: String
//
//   The value at position 1 is not a member of ‚ÄėFiniteNumber‚Äô.

Compare this to the behaviour of Ramda's unchecked equivalent:

R.inc('XXX');
// => NaN

There is a performance cost to run-time type checking. One may wish to disable type checking in certain contexts to avoid paying this cost. create facilitates the creation of a Sanctuary module which does not perform type checking.

In Node, one could use an environment variable to determine whether to perform type checking:

const {create, env} = require('sanctuary');

const checkTypes = process.env.NODE_ENV !== 'production';
const S = create({checkTypes: checkTypes, env: env});

API

create :: { checkTypes :: Boolean, env :: Array Type } -> Module

Takes an options record and returns a Sanctuary module. checkTypes specifies whether to enable type checking. The module's polymorphic functions (such as I) require each value associated with a type variable to be a member of at least one type in the environment.

A well-typed application of a Sanctuary function will produce the same result regardless of whether type checking is enabled. If type checking is enabled, a badly typed application will produce an exception with a descriptive error message.

The following snippet demonstrates defining a custom type and using create to produce a Sanctuary module which is aware of that type:

const {create, env} = require('sanctuary');
const $ = require('sanctuary-def');

//    identityTypeName :: String
const identityTypeName = 'my-package/Identity';

//    Identity :: a -> Identity a
const Identity = function Identity(x) {
  return {
    '@@type': identityTypeName,
    map: f => Identity(f(x)),
    chain: f => f(x),
    // ...
    value: x,
  };
};

//    isIdentity :: a -> Boolean
const isIdentity = x => x != null && x['@@type'] === identityTypeName;

//    identityToArray :: Identity a -> Array a
const identityToArray = identity => [identity.value];

//    IdentityType :: Type
const IdentityType =
$.UnaryType(identityTypeName, isIdentity, identityToArray);

const S = create({
  checkTypes: process.env.NODE_ENV !== 'production',
  env: env.concat([IdentityType]),
});

See also env.

env :: Array Type

The default environment, which may be used as is or as the basis of a custom environment in conjunction with create.

Classify

type :: a -> String

Takes a value, x, of any type and returns its type identifier. If x has a '@@type' property whose value is a string, x['@@type'] is the type identifier. Otherwise, the type identifier is the result of applying R.type to x.

'@@type' properties should use the form '<package-name>/<type-name>', where <package-name> is the name of the npm package in which the type is defined.

> S.type(S.Just(42))
'sanctuary/Maybe'

> S.type([1, 2, 3])
'Array'

is :: TypeRep a -> b -> Boolean

Takes a type representative and a value of any type and returns true if the given value is of the specified type; false otherwise. Subtyping is not respected.

> S.is(Number, 42)
true

> S.is(Object, 42)
false

> S.is(String, 42)
false

Combinator

I :: a -> a

The I combinator. Returns its argument. Equivalent to Haskell's id function.

> S.I('foo')
'foo'

K :: a -> b -> a

The K combinator. Takes two values and returns the first. Equivalent to Haskell's const function.

> S.K('foo', 'bar')
'foo'

> R.map(S.K(42), R.range(0, 5))
[42, 42, 42, 42, 42]

A :: (a -> b) -> a -> b

The A combinator. Takes a function and a value, and returns the result of applying the function to the value. Equivalent to Haskell's ($) function.

> S.A(S.inc, 42)
43

> R.map(S.A(R.__, 100), [S.inc, Math.sqrt])
[101, 10]

T :: a -> (a -> b) -> b

The T (thrush) combinator. Takes a value and a function, and returns the result of applying the function to the value. Equivalent to Haskell's (&) function.

> S.T(42, S.inc)
43

> R.map(S.T(100), [S.inc, Math.sqrt])
[101, 10]

C :: (a -> b -> c) -> b -> a -> c

The C combinator. Takes a curried binary function and two values, and returns the result of applying the function to the values in reverse. Equivalent to Haskell's flip function.

This function is very similar to flip, except that its first argument must be curried. This allows it to work with manually curried functions.

> S.C(S.concat, 'foo', 'bar')
'barfoo'

> R.filter(S.C(R.gt, 0), [-1, -2, 3, -4, 4, 2])
[3, 4, 2]

B :: (b -> c) -> (a -> b) -> a -> c

The B combinator. Takes two functions and a value, and returns the result of applying the first function to the result of applying the second to the value. Equivalent to compose and Haskell's (.) function.

> S.B(Math.sqrt, S.inc, 99)
10

S :: (a -> b -> c) -> (a -> b) -> a -> c

The S combinator. Takes a curried binary function, a unary function, and a value, and returns the result of applying the binary function to:

  • the value; and
  • the result of applying the unary function to the value.
> S.S(S.add, Math.sqrt, 100)
110

Function

flip :: ((a, b) -> c) -> b -> a -> c

Takes a binary function and two values and returns the result of applying the function - with its argument order reversed - to the values. flip may also be applied to a Ramda-style curried function with arity greater than two.

See also C.

> R.map(S.flip(Math.pow)(2), [1, 2, 3, 4, 5])
[1, 4, 9, 16, 25]

lift :: Functor f => (a -> b) -> f a -> f b

Promotes a unary function to a function which operates on a Functor.

> S.lift(S.inc, S.Just(2))
Just(3)

> S.lift(S.inc, S.Nothing())
Nothing()

lift2 :: Apply f => (a -> b -> c) -> f a -> f b -> f c

Promotes a binary function to a function which operates on two Applys.

> S.lift2(S.add, S.Just(2), S.Just(3))
Just(5)

> S.lift2(S.add, S.Just(2), S.Nothing())
Nothing()

> S.lift2(S.and, S.Just(true), S.Just(true))
Just(true)

> S.lift2(S.and, S.Just(true), S.Just(false))
Just(false)

lift3 :: Apply f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d

Promotes a ternary function to a function which operates on three Applys.

> S.lift3(S.reduce, S.Just(S.add), S.Just(0), S.Just([1, 2, 3]))
Just(6)

> S.lift3(S.reduce, S.Just(S.add), S.Just(0), S.Nothing())
Nothing()

Composition

compose :: (b -> c) -> (a -> b) -> a -> c

Takes two functions assumed to be unary and a value of any type, and returns the result of applying the first function to the result of applying the second function to the given value.

In general terms, compose performs right-to-left composition of two unary functions.

See also B and pipe.

> S.compose(Math.sqrt, S.inc)(99)
10

pipe :: [(a -> b), (b -> c), ..., (m -> n)] -> a -> n

Takes an array of functions assumed to be unary and a value of any type, and returns the result of applying the sequence of transformations to the initial value.

In general terms, pipe performs left-to-right composition of an array of functions. pipe([f, g, h], x) is equivalent to h(g(f(x))).

See also meld.

> S.pipe([S.inc, Math.sqrt, S.dec])(99)
9

meld :: [** -> *] -> (* -> * -> ... -> *)

Takes an array of non-nullary functions and returns a curried function whose arity is one greater than the sum of the arities of the given functions less the number of functions.

The behaviour of meld is best conveyed diagrammatically. The following diagram depicts the "melding" of binary functions f and g:

          +-------+
--- a --->|       |
          |   f   |                +-------+
--- b --->|       |--- f(a, b) --->|       |
          +-------+                |   g   |
--- c ---------------------------->|       |--- g(f(a, b), c) --->
                                   +-------+

See also pipe.

> S.meld([Math.pow, S.sub])(3, 4, 5)
76

> S.meld([Math.pow, S.sub])(3)(4)(5)
76

Maybe type

The Maybe type represents optional values: a value of type Maybe a is either a Just whose value is of type a or a Nothing (with no value).

The Maybe type satisfies the Monoid, Monad, Traversable, and Extend specifications.

MaybeType :: Type -> Type

A UnaryType for use with sanctuary-def.

Maybe :: TypeRep Maybe

The type representative for the Maybe type.

Maybe.empty :: -> Maybe a

Returns a Nothing.

> S.Maybe.empty()
Nothing()

Maybe.of :: a -> Maybe a

Takes a value of any type and returns a Just with the given value.

> S.Maybe.of(42)
Just(42)

Maybe#@@type :: String

Maybe type identifier, 'sanctuary/Maybe'.

Maybe#isNothing :: Boolean

true if this is a Nothing; false if this is a Just.

> S.Nothing().isNothing
true

> S.Just(42).isNothing
false

Maybe#isJust :: Boolean

true if this is a Just; false if this is a Nothing.

> S.Just(42).isJust
true

> S.Nothing().isJust
false

Maybe#ap :: Maybe (a -> b) ~> Maybe a -> Maybe b

Takes a value of type Maybe a and returns a Nothing unless this is a Just and the argument is a Just, in which case it returns a Just whose value is the result of of applying this Just's value to the given Just's value.

> S.Nothing().ap(S.Just(42))
Nothing()

> S.Just(S.inc).ap(S.Nothing())
Nothing()

> S.Just(S.inc).ap(S.Just(42))
Just(43)

Maybe#chain :: Maybe a ~> (a -> Maybe b) -> Maybe b

Takes a function and returns this if this is a Nothing; otherwise it returns the result of applying the function to this Just's value.

> S.Nothing().chain(S.parseFloat)
Nothing()

> S.Just('xxx').chain(S.parseFloat)
Nothing()

> S.Just('12.34').chain(S.parseFloat)
Just(12.34)

Maybe#concat :: Semigroup a => Maybe a ~> Maybe a -> Maybe a

Returns the result of concatenating two Maybe values of the same type. a must have a Semigroup (indicated by the presence of a concat method).

If this is a Nothing and the argument is a Nothing, this method returns a Nothing.

If this is a Just and the argument is a Just, this method returns a Just whose value is the result of concatenating this Just's value and the given Just's value.

Otherwise, this method returns the Just.

> S.Nothing().concat(S.Nothing())
Nothing()

> S.Just([1, 2, 3]).concat(S.Just([4, 5, 6]))
Just([1, 2, 3, 4, 5, 6])

> S.Nothing().concat(S.Just([1, 2, 3]))
Just([1, 2, 3])

> S.Just([1, 2, 3]).concat(S.Nothing())
Just([1, 2, 3])

Maybe#empty :: Maybe a ~> Maybe a

Returns a Nothing.

> S.Just(42).empty()
Nothing()

Maybe#equals :: Maybe a ~> b -> Boolean

Takes a value of any type and returns true if:

  • it is a Nothing and this is a Nothing; or

  • it is a Just and this is a Just, and their values are equal according to R.equals.

> S.Nothing().equals(S.Nothing())
true

> S.Nothing().equals(null)
false

> S.Just([1, 2, 3]).equals(S.Just([1, 2, 3]))
true

> S.Just([1, 2, 3]).equals(S.Just([3, 2, 1]))
false

> S.Just([1, 2, 3]).equals(S.Nothing())
false

Maybe#extend :: Maybe a ~> (Maybe a -> a) -> Maybe a

Takes a function and returns this if this is a Nothing; otherwise it returns a Just whose value is the result of applying the function to this.

> S.Nothing().extend(x => x.value + 1)
Nothing()

> S.Just(42).extend(x => x.value + 1)
Just(43)

Maybe#filter :: Maybe a ~> (a -> Boolean) -> Maybe a

Takes a predicate and returns this if this is a Just whose value satisfies the predicate; Nothing otherwise.

> S.Just(42).filter(n => n % 2 === 0)
Just(42)

> S.Just(43).filter(n => n % 2 === 0)
Nothing()

Maybe#map :: Maybe a ~> (a -> b) -> Maybe b

Takes a function and returns this if this is a Nothing; otherwise it returns a Just whose value is the result of applying the function to this Just's value.

> S.Nothing().map(S.inc)
Nothing()

> S.Just([1, 2, 3]).map(S.sum)
Just(6)

Maybe#of :: Maybe a ~> b -> Maybe b

Takes a value of any type and returns a Just with the given value.

> S.Nothing().of(42)
Just(42)

Maybe#reduce :: Maybe a ~> ((b, a) -> b) -> b -> b

Takes a function and an initial value of any type, and returns:

  • the initial value if this is a Nothing; otherwise

  • the result of applying the function to the initial value and this Just's value.

> S.Nothing().reduce(S.add, 10)
10

> S.Just(5).reduce(S.add, 10)
15

Maybe#sequence :: Applicative f => Maybe (f a) ~> (a -> f a) -> f (Maybe a)

Evaluates an applicative action contained within the Maybe, resulting in:

  • a pure applicative of a Nothing if this is a Nothing; otherwise

  • an applicative of Just the value of the evaluated action.

> S.Nothing().sequence(S.Either.of)
Right(Nothing())

> S.Just(S.Right(42)).sequence(S.Either.of)
Right(Just(42))

> S.Just(S.Left('Cannot divide by zero')).sequence(S.Either.of)
Left('Cannot divide by zero')

Maybe#toBoolean :: Maybe a ~> Boolean

Returns false if this is a Nothing; true if this is a Just.

> S.Nothing().toBoolean()
false

> S.Just(42).toBoolean()
true

Maybe#toString :: Maybe a ~> String

Returns the string representation of the Maybe.

> S.Nothing().toString()
'Nothing()'

> S.Just([1, 2, 3]).toString()
'Just([1, 2, 3])'

Maybe#inspect :: Maybe a ~> String

Returns the string representation of the Maybe. This method is used by util.inspect and the REPL to format a Maybe for display.

See also Maybe#toString.

> S.Nothing().inspect()
'Nothing()'

> S.Just([1, 2, 3]).inspect()
'Just([1, 2, 3])'

Nothing :: -> Maybe a

Returns a Nothing.

> S.Nothing()
Nothing()

Just :: a -> Maybe a

Takes a value of any type and returns a Just with the given value.

> S.Just(42)
Just(42)

isNothing :: Maybe a -> Boolean

Returns true if the given Maybe is a Nothing; false if it is a Just.

> S.isNothing(S.Nothing())
true

> S.isNothing(S.Just(42))
false

isJust :: Maybe a -> Boolean

Returns true if the given Maybe is a Just; false if it is a Nothing.

> S.isJust(S.Just(42))
true

> S.isJust(S.Nothing())
false

fromMaybe :: a -> Maybe a -> a

Takes a default value and a Maybe, and returns the Maybe's value if the Maybe is a Just; the default value otherwise.

See also maybeToNullable.

> S.fromMaybe(0, S.Just(42))
42

> S.fromMaybe(0, S.Nothing())
0

maybeToNullable :: Maybe a -> Nullable a

Returns the given Maybe's value if the Maybe is a Just; null otherwise. Nullable is defined in sanctuary-def.

See also fromMaybe.

> S.maybeToNullable(S.Just(42))
42

> S.maybeToNullable(S.Nothing())
null

toMaybe :: a? -> Maybe a

Takes a value and returns Nothing if the value is null or undefined; Just the value otherwise.

> S.toMaybe(null)
Nothing()

> S.toMaybe(42)
Just(42)

maybe :: b -> (a -> b) -> Maybe a -> b

Takes a value of any type, a function, and a Maybe. If the Maybe is a Just, the return value is the result of applying the function to the Just's value. Otherwise, the first argument is returned.

> S.maybe(0, R.length, S.Just('refuge'))
6

> S.maybe(0, R.length, S.Nothing())
0

justs :: Array (Maybe a) -> Array a

Takes an array of Maybes and returns an array containing each Just's value. Equivalent to Haskell's catMaybes function.

See also lefts and rights.

> S.justs([S.Just('foo'), S.Nothing(), S.Just('baz')])
['foo', 'baz']

mapMaybe :: (a -> Maybe b) -> Array a -> Array b

Takes a function and an array, applies the function to each element of the array, and returns an array of "successful" results. If the result of applying the function to an element of the array is a Nothing, the result is discarded; if the result is a Just, the Just's value is included in the output array.

In general terms, mapMaybe filters an array while mapping over it.

> S.mapMaybe(S.head, [[], [1, 2, 3], [], [4, 5, 6], []])
[1, 4]

encase :: (a -> b) -> a -> Maybe b

Takes a unary function f which may throw and a value x of any type, and applies f to x inside a try block. If an exception is caught, the return value is a Nothing; otherwise the return value is Just the result of applying f to x.

See also encaseEither.

> S.encase(eval, '1 + 1')
Just(2)

> S.encase(eval, '1 +')
Nothing()

encase2 :: (a -> b -> c) -> a -> b -> Maybe c

Binary version of encase.

See also encase2_.

encase2_ :: ((a, b) -> c) -> a -> b -> Maybe c

Version of encase2 accepting uncurried functions.

encase3 :: (a -> b -> c -> d) -> a -> b -> c -> Maybe d

Ternary version of encase.

See also encase3_.

encase3_ :: ((a, b, c) -> d) -> a -> b -> c -> Maybe d

Version of encase3 accepting uncurried functions.

maybeToEither :: a -> Maybe b -> Either a b

Converts a Maybe to an Either. A Nothing becomes a Left (containing the first argument); a Just becomes a Right.

See also eitherToMaybe.

> S.maybeToEither('Expecting an integer', S.parseInt(10, 'xyz'))
Left('Expecting an integer')

> S.maybeToEither('Expecting an integer', S.parseInt(10, '42'))
Right(42)

Either type

The Either type represents values with two possibilities: a value of type Either a b is either a Left whose value is of type a or a Right whose value is of type b.

The Either type satisfies the Semigroup, Monad, Traversable, and Extend specifications.

EitherType :: Type -> Type -> Type

A BinaryType for use with sanctuary-def.

Either :: TypeRep Either

The type representative for the Either type.

Either.of :: b -> Either a b

Takes a value of any type and returns a Right with the given value.

> S.Either.of(42)
Right(42)

Either#@@type :: String

Either type identifier, 'sanctuary/Either'.

Either#isLeft :: Boolean

true if this is a Left; false if this is a Right.

> S.Left('Cannot divide by zero').isLeft
true

> S.Right(42).isLeft
false

Either#isRight :: Boolean

true if this is a Right; false if this is a Left.

> S.Right(42).isRight
true

> S.Left('Cannot divide by zero').isRight
false

Either#ap :: Either a (b -> c) ~> Either a b -> Either a c

Takes a value of type Either a b and returns a Left unless this is a Right and the argument is a Right, in which case it returns a Right whose value is the result of applying this Right's value to the given Right's value.

> S.Left('Cannot divide by zero').ap(S.Right(42))
Left('Cannot divide by zero')

> S.Right(S.inc).ap(S.Left('Cannot divide by zero'))
Left('Cannot divide by zero')

> S.Right(S.inc).ap(S.Right(42))
Right(43)

Either#chain :: Either a b ~> (b -> Either a c) -> Either a c

Takes a function and returns this if this is a Left; otherwise it returns the result of applying the function to this Right's value.

> global.sqrt = n =>
.   n < 0 ? S.Left('Cannot represent square root of negative number')
.         : S.Right(Math.sqrt(n))
sqrt

> S.Left('Cannot divide by zero').chain(sqrt)
Left('Cannot divide by zero')

> S.Right(-1).chain(sqrt)
Left('Cannot represent square root of negative number')

> S.Right(25).chain(sqrt)
Right(5)

Either#concat :: (Semigroup a, Semigroup b) => Either a b ~> Either a b -> Either a b

Returns the result of concatenating two Either values of the same type. a must have a Semigroup (indicated by the presence of a concat method), as must b.

If this is a Left and the argument is a Left, this method returns a Left whose value is the result of concatenating this Left's value and the given Left's value.

If this is a Right and the argument is a Right, this method returns a Right whose value is the result of concatenating this Right's value and the given Right's value.

Otherwise, this method returns the Right.

> S.Left('abc').concat(S.Left('def'))
Left('abcdef')

> S.Right([1, 2, 3]).concat(S.Right([4, 5, 6]))
Right([1, 2, 3, 4, 5, 6])

> S.Left('abc').concat(S.Right([1, 2, 3]))
Right([1, 2, 3])

> S.Right([1, 2, 3]).concat(S.Left('abc'))
Right([1, 2, 3])

Either#equals :: Either a b ~> c -> Boolean

Takes a value of any type and returns true if:

  • it is a Left and this is a Left, and their values are equal according to R.equals; or

  • it is a Right and this is a Right, and their values are equal according to R.equals.

> S.Right([1, 2, 3]).equals(S.Right([1, 2, 3]))
true

> S.Right([1, 2, 3]).equals(S.Left([1, 2, 3]))
false

> S.Right(42).equals(42)
false

Either#extend :: Either a b ~> (Either a b -> b) -> Either a b

Takes a function and returns this if this is a Left; otherwise it returns a Right whose value is the result of applying the function to this.

> S.Left('Cannot divide by zero').extend(x => x.value + 1)
Left('Cannot divide by zero')

> S.Right(42).extend(x => x.value + 1)
Right(43)

Either#map :: Either a b ~> (b -> c) -> Either a c

Takes a function and returns this if this is a Left; otherwise it returns a Right whose value is the result of applying the function to this Right's value.

> S.Left('Cannot divide by zero').map(S.inc)
Left('Cannot divide by zero')

> S.Right([1, 2, 3]).map(S.sum)
Right(6)

Either#of :: Either a b ~> c -> Either a c

Takes a value of any type and returns a Right with the given value.

> S.Left('Cannot divide by zero').of(42)
Right(42)

Either#reduce :: Either a b ~> ((c, b) -> c) -> c -> c

Takes a function and an initial value of any type, and returns:

  • the initial value if this is a Left; otherwise

  • the result of applying the function to the initial value and this Right's value.

> S.Left('Cannot divide by zero').reduce((xs, x) => xs.concat([x]), [42])
[42]

> S.Right(5).reduce((xs, x) => xs.concat([x]), [42])
[42, 5]

Either#sequence :: Applicative f => Either a (f b) ~> (b -> f b) -> f (Either a b)

Evaluates an applicative action contained within the Either, resulting in:

  • a pure applicative of a Left if this is a Left; otherwise

  • an applicative of a Right of the evaluated action.

> S.Left('Cannot divide by zero').sequence(S.Maybe.of)
Just(Left('Cannot divide by zero'))

> S.Right(S.Just(42)).sequence(S.Maybe.of)
Just(Right(42))

> S.Right(S.Nothing()).sequence(S.Maybe.of)
Nothing()

Either#toBoolean :: Either a b ~> Boolean

Returns false if this is a Left; true if this is a Right.

> S.Left(42).toBoolean()
false

> S.Right(42).toBoolean()
true

Either#toString :: Either a b ~> String

Returns the string representation of the Either.

> S.Left('Cannot divide by zero').toString()
'Left("Cannot divide by zero")'

> S.Right([1, 2, 3]).toString()
'Right([1, 2, 3])'

Either#inspect :: Either a b ~> String

Returns the string representation of the Either. This method is used by util.inspect and the REPL to format a Either for display.

See also Either#toString.

> S.Left('Cannot divide by zero').inspect()
'Left("Cannot divide by zero")'

> S.Right([1, 2, 3]).inspect()
'Right([1, 2, 3])'

Left :: a -> Either a b

Takes a value of any type and returns a Left with the given value.

> S.Left('Cannot divide by zero')
Left('Cannot divide by zero')

Right :: b -> Either a b

Takes a value of any type and returns a Right with the given value.

> S.Right(42)
Right(42)

isLeft :: Either a b -> Boolean

Returns true if the given Either is a Left; false if it is a Right.

> S.isLeft(S.Left('Cannot divide by zero'))
true

> S.isLeft(S.Right(42))
false

isRight :: Either a b -> Boolean

Returns true if the given Either is a Right; false if it is a Left.

> S.isRight(S.Right(42))
true

> S.isRight(S.Left('Cannot divide by zero'))
false

either :: (a -> c) -> (b -> c) -> Either a b -> c

Takes two functions and an Either, and returns the result of applying the first function to the Left's value, if the Either is a Left, or the result of applying the second function to the Right's value, if the Either is a Right.

> S.either(S.toUpper, R.toString, S.Left('Cannot divide by zero'))
'CANNOT DIVIDE BY ZERO'

> S.either(S.toUpper, R.toString, S.Right(42))
'42'

lefts :: Array (Either a b) -> Array a

Takes an array of Eithers and returns an array containing each Left's value.

See also rights.

> S.lefts([S.Right(20), S.Left('foo'), S.Right(10), S.Left('bar')])
['foo', 'bar']

rights :: Array (Either a b) -> Array b

Takes an array of Eithers and returns an array containing each Right's value.

See also lefts.

> S.rights([S.Right(20), S.Left('foo'), S.Right(10), S.Left('bar')])
[20, 10]

encaseEither :: (Error -> l) -> (a -> r) -> a -> Either l r

Takes two unary functions, f and g, the second of which may throw, and a value x of any type. Applies g to x inside a try block. If an exception is caught, the return value is a Left containing the result of applying f to the caught Error object; otherwise the return value is a Right containing the result of applying g to x.

See also encase.

> S.encaseEither(S.I, JSON.parse, '["foo","bar","baz"]')
Right(['foo', 'bar', 'baz'])

> S.encaseEither(S.I, JSON.parse, '[')
Left(new SyntaxError('Unexpected end of input'))

> S.encaseEither(S.prop('message'), JSON.parse, '[')
Left('Unexpected end of input')

encaseEither2 :: (Error -> l) -> (a -> b -> r) -> a -> b -> Either l r

Binary version of encaseEither.

See also encaseEither2_.

encaseEither2_ :: (Error -> l) -> ((a, b) -> r) -> a -> b -> Either l r

Version of encaseEither2 accepting uncurried functions.

encaseEither3 :: (Error -> l) -> (a -> b -> c -> r) -> a -> b -> c -> Either l r

Ternary version of encaseEither.

See also encaseEither3_.

encaseEither3_ :: (Error -> l) -> ((a, b, c) -> r) -> a -> b -> c -> Either l r

Version of encaseEither3 accepting uncurried functions.

eitherToMaybe :: Either a b -> Maybe b

Converts an Either to a Maybe. A Left becomes a Nothing; a Right becomes a Just.

See also maybeToEither.

> S.eitherToMaybe(S.Left('Cannot divide by zero'))
Nothing()

> S.eitherToMaybe(S.Right(42))
Just(42)

Alternative

and :: Alternative a => a -> a -> a

Takes two values of the same type and returns the second value if the first is "true"; the first value otherwise. An array is considered "true" if its length is greater than zero. The Boolean value true is also considered "true". Other types must provide a toBoolean method.

> S.and(S.Just(1), S.Just(2))
Just(2)

> S.and(S.Nothing(), S.Just(3))
Nothing()

or :: Alternative a => a -> a -> a

Takes two values of the same type and returns the first value if it is "true"; the second value otherwise. An array is considered "true" if its length is greater than zero. The Boolean value true is also considered "true". Other types must provide a toBoolean method.

> S.or(S.Just(1), S.Just(2))
Just(1)

> S.or(S.Nothing(), S.Just(3))
Just(3)

xor :: (Alternative a, Monoid a) => a -> a -> a

Takes two values of the same type and returns the "true" value if one value is "true" and the other is "false"; otherwise it returns the type's "false" value. An array is considered "true" if its length is greater than zero. The Boolean value true is also considered "true". Other types must provide toBoolean and empty methods.

> S.xor(S.Nothing(), S.Just(1))
Just(1)

> S.xor(S.Just(2), S.Just(3))
Nothing()

Logic

not :: Boolean -> Boolean

Takes a Boolean and returns the negation of that value (false for true; true for false).

> S.not(true)
false

> S.not(false)
true

ifElse :: (a -> Boolean) -> (a -> b) -> (a -> b) -> a -> b

Takes a unary predicate, a unary "if" function, a unary "else" function, and a value of any type, and returns the result of applying the "if" function to the value if the value satisfies the predicate; the result of applying the "else" function to the value otherwise.

> S.ifElse(x => x < 0, Math.abs, Math.sqrt, -1)
1

> S.ifElse(x => x < 0, Math.abs, Math.sqrt, 16)
4

allPass :: Array (a -> Boolean) -> a -> Boolean

Takes an array of unary predicates and a value of any type and returns true if all the predicates pass; false otherwise. None of the subsequent predicates will be evaluated after the first failed predicate.

> S.allPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'quiessence')
true

> S.allPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'fissiparous')
false

anyPass :: Array (a -> Boolean) -> a -> Boolean

Takes an array of unary predicates and a value of any type and returns true if any of the predicates pass; false otherwise. None of the subsequent predicates will be evaluated after the first passed predicate.

> S.anyPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'incandescent')
true

> S.anyPass([S.test(/q/), S.test(/u/), S.test(/i/)], 'empathy')
false

List

The List type represents non-Function values with integer length properties greater than or equal to zero, such as [1, 2, 3] and 'foo'.

[a] is the notation used to represent a List of values of type a.

concat :: Semigroup a => a -> a -> a

Concatenates two (homogeneous) arrays, two strings, or two values of any other type which satisfies the Semigroup specification.

> S.concat([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]

> S.concat('foo', 'bar')
'foobar'

> S.concat(S.Just('foo'), S.Just('bar'))
S.Just('foobar')

slice :: Integer -> Integer -> [a] -> Maybe [a]

Returns Just a list containing the elements from the supplied list from a beginning index (inclusive) to an end index (exclusive). Returns Nothing unless the start interval is less than or equal to the end interval, and the list contains both (half-open) intervals. Accepts negative indices, which indicate an offset from the end of the list.

Dispatches to its third argument's slice method if present. As a result, one may replace [a] with String in the type signature.

> S.slice(1, 3, ['a', 'b', 'c', 'd', 'e'])
Just(['b', 'c'])

> S.slice(-2, -0, ['a', 'b', 'c', 'd', 'e'])
Just(['d', 'e'])

> S.slice(2, -0, ['a', 'b', 'c', 'd', 'e'])
Just(['c', 'd', 'e'])

> S.slice(1, 6, ['a', 'b', 'c', 'd', 'e'])
Nothing()

> S.slice(2, 6, 'banana')
Just('nana')

at :: Integer -> [a] -> Maybe a

Takes an index and a list and returns Just the element of the list at the index if the index is within the list's bounds; Nothing otherwise. A negative index represents an offset from the length of the list.

> S.at(2, ['a', 'b', 'c', 'd', 'e'])
Just('c')

> S.at(5, ['a', 'b', 'c', 'd', 'e'])
Nothing()

> S.at(-2, ['a', 'b', 'c', 'd', 'e'])
Just('d')

head :: [a] -> Maybe a

Takes a list and returns Just the first element of the list if the list contains at least one element; Nothing if the list is empty.

> S.head([1, 2, 3])
Just(1)

> S.head([])
Nothing()

last :: [a] -> Maybe a

Takes a list and returns Just the last element of the list if the list contains at least one element; Nothing if the list is empty.

> S.last([1, 2, 3])
Just(3)

> S.last([])
Nothing()

tail :: [a] -> Maybe [a]

Takes a list and returns Just a list containing all but the first of the list's elements if the list contains at least one element; Nothing if the list is empty.

> S.tail([1, 2, 3])
Just([2, 3])

> S.tail([])
Nothing()

init :: [a] -> Maybe [a]

Takes a list and returns Just a list containing all but the last of the list's elements if the list contains at least one element; Nothing if the list is empty.

> S.init([1, 2, 3])
Just([1, 2])

> S.init([])
Nothing()

take :: Integer -> [a] -> Maybe [a]

Returns Just the first N elements of the given collection if N is greater than or equal to zero and less than or equal to the length of the collection; Nothing otherwise. Supports Array, String, and any other collection type which provides a slice method.

> S.take(2, ['a', 'b', 'c', 'd', 'e'])
Just(['a', 'b'])

> S.take(4, 'abcdefg')
Just('abcd')

> S.take(4, ['a', 'b', 'c'])
Nothing()

takeLast :: Integer -> [a] -> Maybe [a]

Returns Just the last N elements of the given collection if N is greater than or equal to zero and less than or equal to the length of the collection; Nothing otherwise. Supports Array, String, and any other collection type which provides a slice method.

> S.takeLast(2, ['a', 'b', 'c', 'd', 'e'])
Just(['d', 'e'])

> S.takeLast(4, 'abcdefg')
Just('defg')

> S.takeLast(4, ['a', 'b', 'c'])
Nothing()

drop :: Integer -> [a] -> Maybe [a]

Returns Just all but the first N elements of the given collection if N is greater than or equal to zero and less than or equal to the length of the collection; Nothing otherwise. Supports Array, String, and any other collection type which provides a slice method.

> S.drop(2, ['a', 'b', 'c', 'd', 'e'])
Just(['c', 'd', 'e'])

> S.drop(4, 'abcdefg')
Just('efg')

> S.drop(4, 'abc')
Nothing()

dropLast :: Integer -> [a] -> Maybe [a]

Returns Just all but the last N elements of the given collection if N is greater than or equal to zero and less than or equal to the length of the collection; Nothing otherwise. Supports Array, String, and any other collection type which provides a slice method.

> S.dropLast(2, ['a', 'b', 'c', 'd', 'e'])
Just(['a', 'b', 'c'])

> S.dropLast(4, 'abcdefg')
Just('abc')

> S.dropLast(4, 'abc')
Nothing()

reverse :: [a] -> [a]

Returns the elements of the given list in reverse order.

> S.reverse([1, 2, 3])
[3, 2, 1]

> S.reverse('abc')
'cba'

indexOf :: a -> [a] -> Maybe Integer

Takes a value of any type and a list, and returns Just the index of the first occurrence of the value in the list, if applicable; Nothing otherwise.

Dispatches to its second argument's indexOf method if present. As a result, String -> String -> Maybe Integer is an alternative type signature.

> S.indexOf('a', ['b', 'a', 'n', 'a', 'n', 'a'])
Just(1)

> S.indexOf('x', ['b', 'a', 'n', 'a', 'n', 'a'])
Nothing()

> S.indexOf('an', 'banana')
Just(1)

> S.indexOf('ax', 'banana')
Nothing()

lastIndexOf :: a -> [a] -> Maybe Integer

Takes a value of any type and a list, and returns Just the index of the last occurrence of the value in the list, if applicable; Nothing otherwise.

Dispatches to its second argument's lastIndexOf method if present. As a result, String -> String -> Maybe Integer is an alternative type signature.

> S.lastIndexOf('a', ['b', 'a', 'n', 'a', 'n', 'a'])
Just(5)

> S.lastIndexOf('x', ['b', 'a', 'n', 'a', 'n', 'a'])
Nothing()

> S.lastIndexOf('an', 'banana')
Just(3)

> S.lastIndexOf('ax', 'banana')
Nothing()

Array

append :: a -> Array a -> Array a

Takes a value of any type and an array of values of that type, and returns the result of appending the value to the array.

See also prepend.

> S.append(3, [1, 2])
[1, 2, 3]

prepend :: a -> Array a -> Array a

Takes a value of any type and an array of values of that type, and returns the result of prepending the value to the array.

See also append.

> S.prepend(1, [2, 3])
[1, 2, 3]

find :: (a -> Boolean) -> Array a -> Maybe a

Takes a predicate and an array and returns Just the leftmost element of the array which satisfies the predicate; Nothing if none of the array's elements satisfies the predicate.

> S.find(n => n < 0, [1, -2, 3, -4, 5])
Just(-2)

> S.find(n => n < 0, [1, 2, 3, 4, 5])
Nothing()

pluck :: Accessible a => TypeRep b -> String -> Array a -> Array (Maybe b)

Takes a type representative, a property name, and an array of objects and returns an array of equal length. Each element of the output array is Just the value of the specified property of the corresponding object if the value is of the specified type (according to is); Nothing otherwise.

See also get.

> S.pluck(Number, 'x', [{x: 1}, {x: 2}, {x: '3'}, {x: null}, {}])
[Just(1), Just(2), Nothing(), Nothing(), Nothing()]

reduce :: Foldable f => (a -> b -> a) -> a -> f b -> a

Takes a curried binary function, an initial value, and a Foldable, and applies the function to the initial value and the Foldable's first value, then applies the function to the result of the previous application and the Foldable's second value. Repeats this process until each of the Foldable's values has been used. Returns the initial value if the Foldable is empty; the result of the final application otherwise.

See also reduce_.

> S.reduce(S.add, 0, [1, 2, 3, 4, 5])
15

> S.reduce(xs => x => [x].concat(xs), [], [1, 2, 3, 4, 5])
[5, 4, 3, 2, 1]

reduce_ :: Foldable f => ((a, b) -> a) -> a -> f b -> a

Version of reduce accepting uncurried functions.

unfoldr :: (b -> Maybe (Pair a b)) -> b -> Array a

Takes a function and a seed value, and returns an array generated by applying the function repeatedly. The array is initially empty. The function is initially applied to the seed value. Each application of the function should result in either:

  • a Nothing, in which case the array is returned; or

  • Just a pair, in which case the first element is appended to the array and the function is applied to the second element.

> S.unfoldr(n => n < 5 ? S.Just([n, n + 1]) : S.Nothing(), 1)
[1, 2, 3, 4]

range :: Integer -> Integer -> Array Integer

Returns an array of consecutive integers starting with the first argument and ending with the second argument minus one. Returns [] if the second argument is less than or equal to the first argument.

> S.range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

> S.range(-5, 0)
[-5, -4, -3, -2, -1]

> S.range(0, -5)
[]

Object

prop :: Accessible a => String -> a -> b

Takes a property name and an object with known properties and returns the value of the specified property. If for some reason the object lacks the specified property, a type error is thrown.

For accessing properties of uncertain objects, use get instead.

> S.prop('a', {a: 1, b: 2})
1

get :: Accessible a => TypeRep b -> String -> a -> Maybe b

Takes a type representative, a property name, and an object and returns Just the value of the specified object property if it is of the specified type (according to is); Nothing otherwise.

The Object type representative may be used as a catch-all since most values have Object.prototype in their prototype chains.

See also gets and prop.

> S.get(Number, 'x', {x: 1, y: 2})
Just(1)

> S.get(Number, 'x', {x: '1', y: '2'})
Nothing()

> S.get(Number, 'x', {})
Nothing()

gets :: Accessible a => TypeRep b -> Array String -> a -> Maybe b

Takes a type representative, an array of property names, and an object and returns Just the value at the path specified by the array of property names if such a path exists and the value is of the specified type; Nothing otherwise.

See also get.

> S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: 42}}})
Just(42)

> S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: '42'}}})
Nothing()

> S.gets(Number, ['a', 'b', 'c'], {})
Nothing()

keys :: StrMap a -> Array String

Returns the keys of the given string map, in arbitrary order.

> S.keys({b: 2, c: 3, a: 1}).sort()
['a', 'b', 'c']

values :: StrMap a -> Array a

Returns the values of the given string map, in arbitrary order.

> S.values({a: 1, c: 3, b: 2}).sort()
[1, 2, 3]

pairs :: StrMap a -> Array (Pair String a)

Returns the key‚Äďvalue pairs of the given string map, in arbitrary order.

> S.pairs({b: 2, a: 1, c: 3}).sort()
[['a', 1], ['b', 2], ['c', 3]]

Number

negate :: ValidNumber -> ValidNumber

Negates its argument.

> S.negate(12.5)
-12.5

> S.negate(-42)
42

add :: FiniteNumber -> FiniteNumber -> FiniteNumber

Returns the sum of two (finite) numbers.

> S.add(1, 1)
2

sum :: Foldable f => f FiniteNumber -> FiniteNumber

Returns the sum of the given array of (finite) numbers.

> S.sum([1, 2, 3, 4, 5])
15

> S.sum([])
0

> S.sum(S.Just(42))
42

> S.sum(S.Nothing())
0

sub :: FiniteNumber -> FiniteNumber -> FiniteNumber

Returns the difference between two (finite) numbers.

> S.sub(4, 2)
2

inc :: FiniteNumber -> FiniteNumber

Increments a (finite) number by one.

> S.inc(1)
2

dec :: FiniteNumber -> FiniteNumber

Decrements a (finite) number by one.

> S.dec(2)
1

mult :: FiniteNumber -> FiniteNumber -> FiniteNumber

Returns the product of two (finite) numbers.

> S.mult(4, 2)
8

product :: Foldable f => f FiniteNumber -> FiniteNumber

Returns the product of the given array of (finite) numbers.

> S.product([1, 2, 3, 4, 5])
120

> S.product([])
1

> S.product(S.Just(42))
42

> S.product(S.Nothing())
1

div :: FiniteNumber -> NonZeroFiniteNumber -> FiniteNumber

Returns the result of dividing its first argument (a finite number) by its second argument (a non-zero finite number).

> S.div(7, 2)
3.5

min :: Ord a => a -> a -> a

Returns the smaller of its two arguments.

Strings are compared lexicographically. Specifically, the Unicode code point value of each character in the first string is compared to the value of the corresponding character in the second string.

See also max.

> S.min(10, 2)
2

> S.min(new Date('1999-12-31'), new Date('2000-01-01'))
new Date('1999-12-31')

> S.min('10', '2')
'10'

max :: Ord a => a -> a -> a

Returns the larger of its two arguments.

Strings are compared lexicographically. Specifically, the Unicode code point value of each character in the first string is compared to the value of the corresponding character in the second string.

See also min.

> S.max(10, 2)
10

> S.max(new Date('1999-12-31'), new Date('2000-01-01'))
new Date('2000-01-01')

> S.max('10', '2')
'2'

Integer

even :: Integer -> Boolean

Returns true if the given integer is even; false if it is odd.

> S.even(42)
true

> S.even(99)
false

odd :: Integer -> Boolean

Returns true if the given integer is odd; false if it is even.

> S.odd(99)
true

> S.odd(42)
false

Parse

parseDate :: String -> Maybe Date

Takes a string and returns Just the date represented by the string if it does in fact represent a date; Nothing otherwise.

> S.parseDate('2011-01-19T17:40:00Z')
Just(new Date('2011-01-19T17:40:00.000Z'))

> S.parseDate('today')
Nothing()

parseFloat :: String -> Maybe Number

Takes a string and returns Just the number represented by the string if it does in fact represent a number; Nothing otherwise.

> S.parseFloat('-123.45')
Just(-123.45)

> S.parseFloat('foo.bar')
Nothing()

parseInt :: Integer -> String -> Maybe Integer

Takes a radix (an integer between 2 and 36 inclusive) and a string, and returns Just the number represented by the string if it does in fact represent a number in the base specified by the radix; Nothing otherwise.

This function is stricter than parseInt: a string is considered to represent an integer only if all its non-prefix characters are members of the character set specified by the radix.

> S.parseInt(10, '-42')
Just(-42)

> S.parseInt(16, '0xFF')
Just(255)

> S.parseInt(16, '0xGG')
Nothing()

parseJson :: TypeRep a -> String -> Maybe a

Takes a type representative and a string which may or may not be valid JSON, and returns Just the result of applying JSON.parse to the string if the result is of the specified type (according to is); Nothing otherwise.

> S.parseJson(Array, '["foo","bar","baz"]')
Just(['foo', 'bar', 'baz'])

> S.parseJson(Array, '[')
Nothing()

> S.parseJson(Object, '["foo","bar","baz"]')
Nothing()

RegExp

regex :: RegexFlags -> String -> RegExp

Takes a RegexFlags and a pattern, and returns a RegExp.

> S.regex('g', ':\\d+:')
/:\d+:/g

regexEscape :: String -> String

Takes a string which may contain regular expression metacharacters, and returns a string with those metacharacters escaped.

Properties:

  • forall s :: String. S.test(S.regex('', S.regexEscape(s)), s) = true
> S.regexEscape('-=*{XYZ}*=-')
'\\-=\\*\\{XYZ\\}\\*=\\-'

test :: RegExp -> String -> Boolean

Takes a pattern and a string, and returns true if the pattern matches the string; false otherwise.

> S.test(/^a/, 'abacus')
true

> S.test(/^a/, 'banana')
false

match :: RegExp -> String -> Maybe (Array (Maybe String))

Takes a pattern and a string, and returns Just an array of matches if the pattern matches the string; Nothing otherwise. Each match has type Maybe String, where a Nothing represents an unmatched optional capturing group.

> S.match(/(good)?bye/, 'goodbye')
Just([Just('goodbye'), Just('good')])

> S.match(/(good)?bye/, 'bye')
Just([Just('bye'), Nothing()])

String

toUpper :: String -> String

Returns the upper-case equivalent of its argument.

See also toLower.

> S.toUpper('ABC def 123')
'ABC DEF 123'

toLower :: String -> String

Returns the lower-case equivalent of its argument.

See also toUpper.

> S.toLower('ABC def 123')
'abc def 123'

trim :: String -> String

Strips leading and trailing whitespace characters.

> S.trim('\t\t foo bar \n')
'foo bar'

words :: String -> Array String

Takes a string and returns the array of words the string contains (words are delimited by whitespace characters).

See also unwords.

> S.words(' foo bar baz ')
['foo', 'bar', 'baz']

unwords :: Array String -> String

Takes an array of words and returns the result of joining the words with separating spaces.

See also words.

> S.unwords(['foo', 'bar', 'baz'])
'foo bar baz'

lines :: String -> Array String

Takes a string and returns the array of lines the string contains (lines are delimited by newlines: '\n' or '\r\n' or '\r'). The resulting strings do not contain newlines.

See also unlines.

> S.lines('foo\nbar\nbaz\n')
['foo', 'bar', 'baz']

unlines :: Array String -> String

Takes an array of lines and returns the result of joining the lines after appending a terminating line feed ('\n') to each.

See also lines.

> S.unlines(['foo', 'bar', 'baz'])
'foo\nbar\nbaz\n'