David Chambers has been developing sanctuary, a functional programming library these goals in mind.
I grew up in New Zealand. I studied graphic design at University and worked briefly as a graphic designer after graduating. I found myself in a cycle of procrastination, guilt, self-doubt, and euphoria. I loved my job a small proportion of the time.
The project's overview puts it the best:
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 is designed to take full advantage of the Fantasy Land specification. Fantasy Land defines some methods, each with accompanying laws.
The inclusion of laws makes Fantasy Land significantly different in nature from Promises/A+. Fantasy Land is a set of well-understood concepts with Wikipedia entries rather than an ad hoc list of requirement for implementors.
Sanctuary provides functions for operating on built-in types:
> S.map(s => s.length)(['foo', 'bar', 'baz', 'quux']) [3, 3, 3, 4]
The neat thing is that
S.map is not defined only for arrays. We can
enter a function's name into the REPL to see the function's type signature:
> S.map map :: Functor f => (a -> b) -> f a -> f b
Don't be put off by this notation. It'll be very helpful once we've learned to read it. Let's do so.
We'll start with
::. The markup can be read as is a member of. For example,
true :: Boolean can be read
true is a member of
How do we represent the types of functions? Let's consider
S.toUpper which takes a string and returns its upper-case equivalent. We would write
S.toUpper :: String -> String.
What is the type of
x => x, the identity function?
String -> String is too restrictive, as the identity function can operate on a value of any type. We write it like so:
// id :: a -> a const id = x => x;
a is a type variable. We can replace it with any type we choose, but we must replace every occurrence of
a in a type signature with the same type.
Before we consider the
Functor f => bit, let's consider a similar function which operates exclusively on arrays:
arrayMap :: (a -> b) -> Array a -> Array b
arrayMap takes as its argument a value of type
(a -> b), a function from
b. It returns a value of type
Array a -> Array b. We would use it like so:
> arrayMap(s => s.length)(['foo', 'bar', 'baz', 'quux']) [3, 3, 3, 4]
Arrays are just one of many structures we may wish to map over. It would be unfortunate to have to define
streamMap, etc. Although necessary for some languages, it is not so in
The method name, in this case, is
fantasy-land/map. We wish to express that
S.map can operate on a value of any type which provides a
Functor f => is a constraint on the type variable
f. It states that
f can be replaced with any type which provides
Let's specialize the type of
S.map in the expression
S.map(s => s.length)(['foo', 'bar', 'baz', 'quux']). Here's the general type again:
S.map :: Functor f => (a -> b) -> f a -> f b
Array, in this case:
S.map :: (a -> b) -> Array a -> Array b
String, as we're operating on an array of strings:
S.map :: (String -> b) -> Array String -> Array b
Number, since the provided function returns the length of its string argument:
S.map :: (String -> Number) -> Array String -> Array Number
Sanctuary is most similar to Ramda. In sanctuary-js/sanctuary#377 we're currently documenting the differences between Ramda and Sanctuary.
The most striking difference is the handling of invalid inputs. When applied to arguments of the correct types,
S.append are equivalent:
R.append(3, [1, 2]) // => [1, 2, 3] S.append(3, [1, 2]) // => [1, 2, 3]
But what happens if we get the arguments in the wrong order?
R.append([1, 2], 3) // => [[1, 2]]
R.append somehow coerces
 and "succeeds". We expected the result to be of type
Array Number, but it's actually of type
Array (Array Number). This will likely result in an error being thrown, possibly several steps from the source of the type error.
S.append, on the other hand, throws a type error with a descriptive message:
S.append([1, 2], 3) // ! TypeError: Invalid value // // append :: a -> Array a -> Array a // ^^^^^^^ // 1 // // 1) 3 :: Number, FiniteNumber, NonZeroFiniteNumber, Integer, ValidNumber // // The value at position 1 is not a member of ‘Array a’. // // See https://github.com/sanctuary-js/sanctuary-def/tree/v0.9.0#Array for information about the Array type.
Run-time type checking isn't free, so one can disable it if desired.
I was a significant contributor to Ramda at the time, but I was unhappy with the fact that a certain Ramda function was not composable (and remains so). To use
R.head safely one must first check that the array is non-empty:
R.isEmpty(xs) ? /* some default value */ : f(R.head(xs))
I wanted to be able to write this instead:
We decided not to pursue this approach in ramda/ramda#683. I created Sanctuary so I could use safe versions of the Ramda functions I found troubling alongside the many Ramda functions I was happy to use.
Over the past two years, Sanctuary has grown to the point that it no longer makes sense to use Ramda and Sanctuary together, as there's so much overlap in the functionality they provide.
Sanctuary v0.12.0 was a big release representing about eight months' work. From this point forward we hope to make smaller, more frequent releases.
The significant changes currently in the works are sanctuary-js/sanctuary-maybe#3 and sanctuary-js/sanctuary-either#3, which will make Sanctuary's
Either type available as stand-alone packages.
Aldwin Vlasblom, the creator of Fluture. I respect him as a programmer and as a person. :)
If you decide to start using type signatures to document your functions you may be interested in Transcribe. The tool used to generate the readme files for the various Sanctuary projects.
Each type signature in the source code is turned into a heading in the readme which links back to the type signature in the source code, so it's easy to jump to an implementation when reading the documentation. Transcribe supports arbitrary GitHub Flavored Markdown.
One other project I'd like to plug is xyz. If you've ever forgotten a step when publishing an npm package, you should give xyz a look.
Thanks for the interview David! It's cool to see functional solutions get more attention from the community. I see it as a great technique and enjoy spending time composing.
You can learn more about sanctuary at Sanctuary site and GitHub project page.