MCPcopy
hub / github.com/sanctuary-js/sanctuary

github.com/sanctuary-js/sanctuary @v3.1.0 sqlite

repository ↗ · DeepWiki ↗ · release v3.1.0 ↗
117 symbols 390 edges 181 files 0 documented · 0%
README

❑ Sanctuary

npm CircleCI Gitter

Sanctuary is a JavaScript functional programming library inspired by [Haskell][] and [PureScript][]. It's stricter than [Ramda][], and provides a similar suite of functions.

Sanctuary promotes programs composed of simple, pure functions. Such programs are easier to comprehend, test, and maintain – they are also a pleasure to write.

Sanctuary provides two data types, [Maybe][] and [Either][], both of which are compatible with [Fantasy Land][]. Thanks to these data types even Sanctuary functions that may fail, such as head, are composable.

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:

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

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

❑ 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 that 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 that 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][].

❑ Information preservation

Certain Sanctuary functions preserve more information than their Ramda counterparts. Examples:

|> R.tail ([])                      |> S.tail ([])
[]                                  Nothing

|> R.tail (['foo'])                 |> S.tail (['foo'])
[]                                  Just ([])

|> R.replace (/^x/) ('') ('abc')    |> S.stripPrefix ('x') ('abc')
'abc'                               Nothing

|> R.replace (/^x/) ('') ('xabc')   |> S.stripPrefix ('x') ('xabc')
'abc'                               Just ('abc')

❑ 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][GIGO] 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.__][], that removes the restriction that a function must be applied to its arguments in order. The following expressions are equivalent:

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

❑ Variadic functions

Ramda provides several functions that take any number of arguments. These are known as [variadic functions][]. Additionally, Ramda provides several functions that 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 that take variadic functions as arguments. Sanctuary provides two "lift" functions, each with a helpful type signature:

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

❑ Implicit context

Ramda provides [R.bind][] and [R.invoker][] for working with methods. Additionally, many Ramda functions use Function#call or Function#apply to preserve context. Sanctuary makes no allowances for this.

❑ Transducers

Several Ramda functions act as transducers. Sanctuary provides no support for transducers.

❑ Modularity

Whereas Ramda has no dependencies, Sanctuary has a modular design: [sanctuary-def][] provides type checking, [sanctuary-type-classes][] provides Fantasy Land functions and type classes, [sanctuary-show][] provides string representations, and algebraic data types are provided by [sanctuary-either][], [sanctuary-maybe][], and [sanctuary-pair][]. Not only does this approach reduce the complexity of Sanctuary itself, but it allows these components to be reused in other contexts.

❑ 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 that 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 that 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.

Sanctuary embraces types. JavaScript doesn't support algebraic data types, but these can be simulated by providing a group of data constructors that 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 that 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 (=>).

❑ 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.add (2) (true);
// ! TypeError: 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.22.0#FiniteNumber for information about the FiniteNumber type.

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

R.add (2) (true);
// => 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:

const S = sanctuary.create ({
  checkTypes: localStorage.getItem ('SANCTUARY_CHECK_TYPES') === 'true',
  env: sanctuary.env,
});

Occasionally one may wish to perform an operation that is not type safe, such as mapping over an object with heterogeneous values. This is possible via selective use of unchecked functions.

❑ Installation

npm install sanctuary will install Sanctuary for use in Node.js.

To add Sanctuary to a website, add the following <script> element, replacing X.Y.Z with a version number greater than or equal to 2.0.2:

<script src="https://cdn.jsdelivr.net/gh/sanctuary-js/sanctuary@X.Y.Z/dist/bundle.js"></script>

Optionally, define aliases for various modules:

const S = window.sanctuary;
const $ = window.sanctuaryDef;
// ...

❑ API

❑ Configure

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-type

Core symbols most depended-on inside this repo

f
called by 114
test/unfoldr.js
curry2
called by 21
index.js
equals
called by 16
index.js
B
called by 13
index.js
Sum
called by 13
test/internal/Sum.js
map
called by 8
index.js
curry3
called by 6
index.js
C
called by 5
index.js

Shape

Function 117

Languages

TypeScript100%

Modules by API surface

index.js112 symbols
test/unfoldr.js1 symbols
test/type.js1 symbols
test/internal/Sum.js1 symbols
test/internal/List.js1 symbols
test/foldMap.js1 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

Dependencies from manifests, versioned

fantasy-land4.0.1 · 1×
jsverify0.8.x · 1×
sanctuary-def0.22.0 · 1×
sanctuary-descending2.1.0 · 1×
sanctuary-either2.1.0 · 1×
sanctuary-identity2.1.0 · 1×
sanctuary-maybe2.1.0 · 1×
sanctuary-pair2.1.0 · 1×
sanctuary-scripts4.0.x · 1×
sanctuary-show2.0.0 · 1×
sanctuary-type-classes12.1.0 · 1×
sanctuary-type-identifiers3.0.0 · 1×

For agents

$ claude mcp add sanctuary \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact