I've been away from this group a while, and probably will not be
around for long this time, as I'm pretty buried in several projects
that haven't left me much spare time. But I wanted to announce my
current project. I'll check in regularly for a while to read responses.
I'm looking for feedback on a new functional programming library for
Javascript that I've been developing with a friend. This may seem
very familiar to those who saw a similar request a year ago. That was
for a different library, which turned out to be more of academic
interest. This one was originally based on that project, eweda, but
with a more practical bent.
The library is called "ramda", which is just a silly pun on "lambda".
My co-author has just published a brief introduction at
<
http://buzzdecafe.github.io/code/2014/05/16/introducing-ramda/>
The project is on Github, at
<
https://github.com/CrossEye/ramda>
and the library is (temporarily, at least) in a single source file:
<
https://github.com/CrossEye/ramda/blob/master/ramda.js>
You can install it for a Node.js project with
npm install ramda
------------------------------
The API has a sizable overlap with Underscore [1] and LoDash [2]. But
its motivations are closer to
allong.es [3] or Lemonad [4] or
Functional Javascript [5]. It's an attempt to do in Javascript
something more akin to normal functional programming, as would be
found in Haskell, ML, Scheme, or LISP.
The library's functions are available as properties on the main
object, `ramda`:
var square = function(x) {return x * x;};
var nbrs = ramda.range(1, 5); // => [1, 2, 3, 4]
var squares = ramda.map(square, nbrs); // => [1, 4, 9, 16]
But they can be attached to any object, including the global object
via `ramda.installTo()`. This will pollute the global namespace with
a number of functions, but is one possible means of using the library.
After that, you could just call
var squares2 = map(square, range(1, 5)); // => [1, 4, 9, 16]
You might note something surprising here. Even when it has the same
functions as Underscore/LoDash, its parameter order often differs.
This is intentional. Underscore's API was built out of the native,
OO- first functions on `Array.prototype` and other similar structures.
Ramda's API, on the other hand is designed around composition and
partial application (or currying.) You can create new functions
easily by not passing all the arguments to something like `map`:
var squareAll = map(square);
squareAll(range(1, 6)); // => [1, 4, 9, 16, 25]
In all cases, functions that take one list as a parameter, have it as
their last parameter. And those that take one function as a parameter
have it as their first one. The API should be internally consistent,
even if it's a bit different from other libraries.
------------------------------
There is a fairly large set of tests (using Mocha), but they are by no
means comprehensive, serving more as an API guide than as an
exhaustive analysis of edge cases. They are in the folder `tests`,
and can be run in a modern browser at
<
https://rawgit.com/CrossEye/ramda/master/test/index.html>
The only documentation beyond the test suite is a Docco-annotated
version of the source, available at
<
https://rawgit.com/CrossEye/ramda/master/docs/ramda.html>
------------------------------
The functions we include are mostly standard functional library list
functions, ones like `map`, `foldl`, `foldr`, `filter`, `take`,
`skip`, some object-based ones like `prop`and `func`, plain function
manipulations like `compose`, `flip`, `memoize` and various partials,
as well as a few arithmetic and logic functions. There are over 100
functions now, with some dozen aliases. (`reduce` is the same as
`foldl`.)
I'd love for experienced people to look at the functions and see what
is missing, what is totally unnecessary, and what could use a better
name or at least an alias. In the latter category, I really would
like to find a better name for `foldl1`, whose name was borrowed from
Haskell. It's the difference between a left fold with an explicit
accumulator supplied:
var total = foldl(add, 0, range(1, 11)) // => 55
and one where the first value of the (non-empty) list is taken as the
accumulator:
var total2 = foldl1(add, range(1, 11)); // => 55
With the automatic partial application of functions, there is no good
way to allow for dynamic functions signatures, so I can't simply
assume that `foldl` with two parameters means something different from
`foldl` with one. So a good name to replace `foldl1` would be very
helpful. Similarly, of course, for `foldr1`.
------------------------------
We also have a few functions that we think are unique to Ramda. Two
examples are:
- `when` is similar to functions in other libraries that accept an
object specification and return a predicate that can be used to
test additional objects. Ramda's `when` takes more than just
individual values for specification, though; it can also take
functions:
isTriangular = where({
sides: 3,
a: function(a, t) {return a < t.b + t.c;},
b: function(b, t) {return b < t.a + t.c;},
c: function(c, t) {return c < t.a + t.b;}
});
isTriangular({sides: 5}); //=> false
isTriangular({sides: 3, a: 2, b: 1, c: 7}); //=> false
isTriangular({sides: 3, a: 3, b: 4, c: 5}); //=> true
- `useWith`. We still haven't decided which of the two APIs
for this function we prefer, and for the moment are including
both. This function combines a number of configuration functions
with a single gathering function and returns a new function. When
the resulting function is called, the arguments are supplied,
respectively, to each of the configuration functions, and the
results of these functions are all passed to the gathering
function. This allows us to define Codd's `project` (similar to
SQL `select`) for lists of objects with quite simple code:
project = useWith(map, pickAll, identity); // OR (other API)
project = use(map).over(pickAll, identity);
This would get used like this:
var kids = [
{name: 'Abby', age: 7, hair: 'blond'},
{name: 'Fred', age: 12, hair: 'brown'},
{name: 'Rusty', age: 10, hair: 'brown'},
{name: 'Alois', age: 15, disposition: 'surly'}
];
filter(propEq("hair", "brown"), kids);
//=> Fred and Rusty
------------------------------
The library has a facility for creating lazy infinite lists; we're in
the process of pulling that out of the core and making it a separate
extension. In fact we want to have a bundle-building mechanism to
allow you to create your own package, the way Modernizr and David
Mark's MyLibrary (is that still around?) do.
But that infrastructure work has never really made it to the top of
our list of priorities. We've just picked up a volunteer to help with
documentation, so soon the library might be better documented.
But we're still mostly focused on code. And perhaps the biggest thing
left is to implement some of the standard algebraic types, such as
Functor, Applicative, Monoid, and Monad, as well as some standard
objects implementing those, such as Maybe, Either, and IO. This is
well underway, and will probably be ready within weeks.
------------------------------
In any case, this is a very long post. I'd love to hear any feedback
you have to offer. I'd especially love to hear suggestions about the
API design.
Thanks,
-- Scott,
_____
[1]
http://underscorejs.org/
[2]
http://lodash.com/
[3]
https://github.com/raganwald/allong.es
[4]
http://fogus.github.io/lemonad/
[5]
http://osteele.com/posts/2007/07/functional-javascript