Folktale

Folktale, like Sanctuary, is a standard library for functional
programming in JavaScript. It is well designed and well documented.
Whereas Sanctuary treats JavaScript as a member of the ML language
family, Folktale embraces JavaScript's object-oriented programming
model. Programming with Folktale resembles programming with Scala.

Ramda

Ramda provides several functions which return problematic values
such as undefined, Infinity, or NaN when applied to unsuitable
inputs. These are known as partial functions. Partial functions
necessitate the use of guards or null checks. In order to safely use
R.head, for example, one must ensure that the array is non-empty:

if (R.isEmpty (xs)) {
// ...
} else {
return f (R.head (xs));
}

Using the Maybe type renders such guards (and null checks) unnecessary.
Changing functions such as R.head to return Maybe values was proposed
in ramda/ramda#683, but was considered too much of a stretch for
JavaScript programmers. Sanctuary was released the following month,
in January 2015, as a companion library to Ramda.

In addition to broadening in scope in the years since its release,
Sanctuary's philosophy has diverged from Ramda's in several respects.

Totality

Every Sanctuary function is defined for every value which is a member of
the function's input type. Such functions are known as total functions.
Ramda, on the other hand, contains a number of partial functions.

Invariants

Sanctuary performs rigorous type checking of inputs and outputs, and
throws a descriptive error if a type error is encountered. This allows bugs
to be caught and fixed early in the development cycle.

Ramda operates on the garbage in, garbage out principle. Functions
are documented to take arguments of particular types, but these invariants
are not enforced. The problem with this approach in a language as
permissive as JavaScript is that there's no guarantee that garbage input
will produce garbage output (ramda/ramda#1413). Ramda performs ad hoc
type checking in some such cases (ramda/ramda#1419).

Sanctuary can be configured to operate in garbage in, garbage out mode.
Ramda cannot be configured to enforce its invariants.

Currying

Sanctuary functions are curried. There is, for example, exactly one way to
apply S.reduce to S.add, 0, and xs:

S.reduce (S.add) (0) (xs)

Ramda functions are also curried, but in a complex manner. There are four
ways to apply R.reduce to R.add, 0, and xs:

R.reduce (R.add) (0) (xs)

R.reduce (R.add) (0, xs)

R.reduce (R.add, 0) (xs)

R.reduce (R.add, 0, xs)

Ramda supports all these forms because curried functions enable partial
application, one of the library's tenets, but f(x)(y)(z) is considered
too unfamiliar and too unattractive to appeal to JavaScript programmers.

Sanctuary's developers prefer a simple, unfamiliar construct to a complex,
familiar one. Familiarity can be acquired; complexity is intrinsic.

The lack of breathing room in f(x)(y)(z) impairs readability. The simple
solution to this problem, proposed in #438, is to include a space when
applying a function: f (x) (y) (z).

Ramda also provides a special placeholder value, R.__, which removes
the restriction that a function must be applied to its arguments in order.
The following expressions are equivalent:

Variadic functions

Ramda provides several functions which take any number of arguments. These
are known as variadic functions. Additionally, Ramda provides several
functions which take variadic functions as arguments. Although natural in
a dynamically typed language, variadic functions are at odds with the type
notation Ramda and Sanctuary both use, leading to some indecipherable type
signatures such as this one:

R.lift :: (*... -> *...) -> ([*]... -> [*])

Sanctuary has no variadic functions, nor any functions which take variadic
functions as arguments. Sanctuary provides two "lift" functions, each with
a helpful type signature:

Types

Sanctuary uses Haskell-like type signatures to describe the types of
values, including functions. 'foo', for example, is a member of String;
[1, 2, 3] is a member of Array Number. The double colon (::) is used
to mean "is a member of", so one could write:

'foo' :: String
[1, 2, 3] :: Array Number

An identifier may appear to the left of the double colon:

Math.PI :: Number

The arrow (->) is used to express a function's type:

Math.abs :: Number -> Number

That states that Math.abs is a unary function which takes an argument
of type Number and returns a value of type Number.

Some functions are parametrically polymorphic: their types are not fixed.
Type variables are used in the representations of such functions:

S.I :: a -> a

a is a type variable. Type variables are not capitalized, so they
are differentiable from type identifiers (which are always capitalized).
By convention type variables have single-character names. The signature
above states that S.I takes a value of any type and returns a value of
the same type. Some signatures feature multiple type variables:

S.K :: a -> b -> a

It must be possible to replace all occurrences of a with a concrete type.
The same applies for each other type variable. For the function above, the
types with which a and b are replaced may be different, but needn't be.

Since all Sanctuary functions are curried (they accept their arguments
one at a time), a binary function is represented as a unary function which
returns a unary function: * -> * -> *. This aligns neatly with Haskell,
which uses curried functions exclusively. In JavaScript, though, we may
wish to represent the types of functions with arities less than or greater
than one. The general form is (<input-types>) -> <output-type>, where
<input-types> comprises zero or more comma–space (, )
-separated type representations:

() -> String

(a, b) -> a

(a, b, c) -> d

Number -> Number can thus be seen as shorthand for (Number) -> Number.

The question mark (?) is used to represent types which include null
and undefined as members. String?, for example, represents the type
comprising null, undefined, and all strings.

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 Either type, for
example, is created via the Left constructor or the Right 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 fantasy-land/map method
of the Maybe type is written Maybe a ~> (a -> b) -> Maybe b. One could
read this as:

When the fantasy-land/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.

The squiggly arrow is also used when representing non-function properties.
Maybe a ~> Boolean, for example, represents a Boolean property of a value
of type Maybe a.

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
(=>).

! Invalid value
add :: FiniteNumber -> FiniteNumber -> FiniteNumber
^^^^^^^^^^^^
1
1) true :: Boolean
The value at position 1 is not a member of ‘FiniteNumber’.
See https://github.com/sanctuary-js/sanctuary-def/tree/v0.18.1#FiniteNumber for information about the sanctuary-def/FiniteNumber type.

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

3

There is a performance cost to run-time type checking. Type checking is
disabled by default if process.env.NODE_ENV is 'production'. If this
rule is unsuitable for a given program, one may use create
to create a Sanctuary module based on a different rule. For example:

API

Configure

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:

A complete Sanctuary module which performs no type checking. This is
useful as it permits operations which Sanctuary's type checking would
disallow, such as mapping over an object with heterogeneous values.

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.

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

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 Nothing; otherwise the return value is Just the
result of applying f to x.

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.

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.

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.

Takes a structure containing zero or more predicates, and a value
of any type. Returns trueiff the value satisfies all of the
predicates. None of the subsequent predicates will be applied after
the first predicate not satisfied.

Takes a structure containing zero or more predicates, and a value
of any type. Returns trueiff the value satisfies any of the
predicates. None of the subsequent predicates will be applied after
the first predicate satisfied.

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:

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.

Splits its array argument into an array of arrays of equal,
adjacent elements. Equality is determined by the function
provided as the first argument. Its behaviour can be surprising
for functions that aren't reflexive, transitive, and symmetric
(see equivalence relation).

Takes a predicate, a property path (an array of property names), and
an object and returns Just the value at the given path if such a path
exists and the value satisfies the given predicate; Nothing otherwise.

StrMap

StrMap is an abbreviation of string map. A string map is an object,
such as {foo: 1, bar: 2, baz: 3}, whose values are all members of
the same type. Formally, a value is a member of type StrMap a if its
type identifier is 'Object' and the values of its enumerable own
properties are all members of type a.

Takes a string, a value of any type, and a string map, and returns a
string map comprising all the entries of the given string map plus the
entry specified by the first two arguments (which takes precedence).

Equivalent to Haskell's insert function. Similar to Clojure's assoc
function.

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.