We've discussed it a number of times, most recently in #1269: there is a level of documentation simply missing for Ramda: the tutorial-style description of many of its core capabilities.
I believe it's time to start building this. It seems to me that the most logical place to do this is on the GitHub wiki. I'm envisioning a series of brief descriptions of capabilities of of related functions -- something that would take 5 - 10 minutes each to read, organized into a coherent logical picture of Ramda.
This really should be a team writing effort though, so I want to know if the idea seems worthwhile, and if there are people willing to contribute. Below is an (almost-complete) rough draft of a table of contents for such a manual. I figure people could take a section at a time, and we don't have to build it in order. Whatever people find interesting to work on would be helpful.
This would not be a comprehensive guide to every function. The API documentation is meant to do that. This is meant to give a more in-depth view of how you would use various (groups of) functions from Ramda, and how they all hang together.
Formatting:
compose*, pipe*, converge, useWithflip, partial, lift, nAry, apply, unapplyapply, call, bind, memoizetap, wrap, invoker range, repeat, times, keys, toPairstake, drop, append, prepend, concatadjust, updatemap, pluck, filter, aperture, uniq, zip, partitionreduce, find, nth, all/anyfromPairs, createMapEntry, merge, cloneprop, path, pick, `omit, propEq, has, whereassoc, dissocmapObj, project, evolve dec, inc, negate add, subract, modulo, multiply, dividemean, median, product, sumand, or, notboth, either, complement, allPass, anyPassifElse, condOnce we hash out what we want in this table of contents, I'd be willing to get the ball rolling by writing the first half-dozen sections to try to establish a tone and some conventions for the overall document.
So, _first_ does this sound like a good idea? _Second_, what changes do you suggest to the TOC above? _Third_, what sort of help are you willing to give? Are there sections you'd feel comfortable writing?
- [ ] Other libraries - How do they work with or compare to Ramda?
I think it's better if yall focus on what Ramda does. From what I've seen when other libs try the x vs. y thing they tend to miss out on ins and outs of the other libs because they aren't experts in them.
I'll take these:
- [ ] Other libraries - How do they work with or compare to Ramda?
I think it's better if yall focus on what Ramda does. From what I've seen when other libs try the x vs. y thing they tend to miss out on ins and outs of the other libs because they aren't experts in them.
The whole document is meant to be a how-to, and never proselytizing. This specific idea is not meant to discuss anything like "here's why Bilby is better than Trine." Instead, a library like Sanctuary is, by design, a logical companion for Ramda. It would be good to show how to use them together. Ramda's dispatch will allow us to work reasonably well with Immutable.js, but not at all with mori. I would like a paragraph or two to explain why this is so.
Then I would like a very simple description of the differences between Ramda and, say, FKit ("They're quite close, except for these few factors: ...") or between Ramda and Underscore ("The largest differences have to do with parameter order and...") A number of people have mentioned using Ramda with libraries like Bacon and RxJs; I would love to include some pointers about how to do so; Flyd was created by one of our own. I imagine it integrates quite well; but I would like details.
Those are my thoughts, at least.
Thanks @davidchambers! Did you want the topics under "working with objects" and "working with logic and flow" or just the top-level?
I'll take the children as well. :)
I can write something about Highland if that's any use but I know nothing about Lazy or Iz.
@svozza That's fine. Over the weekend, I'll put shells of all the pages up, and then people can contribut what they can to each.
Over the weekend, I'll put shells of all the pages up, and then people can contribut what they can to each.
Ok, I didn't quite manage it "over the weekend." I have most of the infrastructure in place to build these things with Gitbooks, but I'm going to bed now. I will try to finish it tomorrow evening.
I don't know that Gitbooks will be our best solution, but it's nice for an out-of-the box tool to build a decent manual. And the documents are plain Markdown, so they should be reusable if we just want them on the wiki. I didn't like the wiki as I couldn't find a way to give us a nice Table of Contents for the manual section alone. Everything was alphabetic. If someone knows a way to do that besides prefixing everything with 01, 02, etc., please let me know.
Also if anyone has experience with Gitbooks and knows anything about altering templates, I'd love to talk about it. It would be great to make these pages look like our other documentation.
RE the Wiki, couldn't you just create a Home page and manually curate the ToC? Though I would personally prefer Wikibooks/Jekyll/etc (you could easily add an inline REPL, and PR's for GitHub Wikis can be a little screwy).
I wrote a long detailed comment last night, but was very tired when I finished. I think I left it in preview mode. I will try to get it back this evening. But for the moment:
This is a terrific starting point, @CrossEye!
This is the comment I _thought_ I'd posted last night. The wonders of sleepiness! :smile:
Ok, so I've managed a start at this. I'd like some feedback before I proceed too far.
I've added a folder to the docs repo, and one npm build step to generate a Gitbook static webpage from the Markdown files in that folder.
The output is at online but there are no links to it for now. I'd rather keep it that way until our first pass at the manual is done. This should be manageable through PRs and issues, which I definitely like. And the Markdown publishing format is to me ideal.
To build it, you will need to npm install, which will add Gitbooks and its dependencies. If you also want to generate a pdf version, you will also have to install Calibre-ebook. (This might make it difficult to generate PDFs or various ebook formats from Travis, since that install is a binary install, and not a simple NPM package. I don't find this a big deal, as these other formats weren't my main motivation.)
In developing locally, I found it easiest to have Gitbooks serve my documents live, which I did via:
$ node node_modules/gitbook-cli/bin/gitbook serve manual
... info
Serving book on http://localhost:4000
('manual' here is the name of the folder, not a flag.)
If you have Calibre installed, and want to create a PDF, you can do
$ node node_modules/gitbook-cli/bin/gitbook pdf manual
And just a plain build of the static site, if for some reason you don't want to run npm run manual, can be accomplished with
$ node node_modules/gitbook-cli/bin/gitbook build manual
So far, I've only built the first few pages, and the first two are introductory, so it's the third one that I'm hoping will mostly help serve as a model for other pages. I'd really like to have a coherent style across the manual. The following points are, of course, up for discussion, but they form my initial ideas of what the manual should be:
const and let over var; fat-arrow functions; rest parameters and spread operators. But we should be careful only to choose those features that actually increase readability. While destructuring is powerful and useful, I would suggest our examples use at the most the simplest array destructuring available.Those are my ideas. I apologize for checking them in without review. There was only one existing file affected, though, so I hope it's not that too great a _faux pas_.
I'd love to hear what people think. Is this a reasonable format? Would the wiki be better?
It's great to have a list of guidelines (all of which seem reasonable to me).
I've posted dummy pages for everything. Anyone who wants to pitch in is welcome!
I'm still not sure if there are subsections to be added to the Strings chapter, or what, if anything would go in the Miscellaneous chapter.
Added the section on Immutability. Going to update the list above with strikethrough for completion, checkmark for claimed.
I would be very interested in reading the section _Other libraries_ – especially the part about FRP libs.
That documentation has been stalled a long time, and the FRP section is going to be one of the more difficult ones to get to and write, not without a lot more experimentation. Any help would be welcome!
@CrossEye - I would be happy to take a look at FRP section for RxJs. Please leave that with me, if I get some time tomorrow then I will write something.
When I take a look at the items listed under _Other libraries_ then for me personally, those that really stand out are lodash / underscore, immutableJs, and RxJs. Obviously mileage may vary for others.
Lets first discard Lodash / Underscore. The only time I use underscore these days is for the templating capabilities, probably available in lodash but I have been too lazy to check. So that is gone. As for lodash, would argue that lodash is largely a competing alternative to Ramda rather than complimentary - so the concept of talking further about how they work with Ramda is somewhat of a non sequitur. Anyone, please point out known exceptions if you feel there is a genuine need to actually document lodash interplay! Part of my reasoning here is to get others opinions.
So lets start with ImmutableJs. I am really starting to like this library, and find the List, Map and Seq data structures the most useful to date (early days). I thought I'd illustrate the Ramda / ImmutableJs interplay via a test case with some contrived yet illuminating examples. What I have discovered so far is that these Collections come with their own versions of a lot of the usual suspect Collections FP functions. For instance .map, .filter, .take and so on. So the question then becomes, well when would I need to use Ramda? Well, good question. Depending on your style of programming, you could actually omit ramda compose in favour of chaining functions off the built-in Immutable JS List functions for instance.
Ok.. so if I really, really want to use point-free and Ramda, can I? The answer appears to be yes, certainly for some Ramda ops. See examples that follow.
@CrossEye - be grateful if you can expand on your comment above (quoted below) and provide better understanding of what the Ramda dispatch covers here:
Ramda's dispatch will allow us to work reasonably well with Immutable.js
What I did find, is that the Ramda-Lense library plays really nicely with the Immutable.Map api for dredging up and running CRUD ops across nested objects and arrays within the Immutable.Map.
So here are some examples to get started with. Have put in some vanilla cases, so you can see how various libraries work in isolation, and then interplaying between libraries. This was a 20 mins hackathon so apologies if the some of the test names etc are pretty poor quality.
Also with regards to the Ramda and Ramda-Lense libraries, I am getting confused why Ramda contains some of those - lensProp, lensIndex etc ?
'use strict';
const chai = require('chai'),
expect = chai.expect;
describe('Ramda and ImmutableJs interplay', () => {
const assert = require('assert'),
R = require('ramda'),
Immutable = require('immutable'),
Map = Immutable.Map,
List = Immutable.List,
RL = require('ramda-lens'),
view = RL.view,
lens = RL.lens,
over = RL.over,
lensProp = R.lensProp,
lensIndex = R.lensIndex;
describe('ramda using identical operations, one with Array, other with List', () => {
const appendWorld = R.map(R.flip(R.concat)(' world'));
it('should return an array after R.map operation', () => {
const greetings = ['hello', 'howdy'];
expect(appendWorld(greetings)).to.be.instanceOf(Array);
// [ 'hello world', 'howdy world' ]
});
it('should return an Immutable List after R.map operation', () => {
const greetings = List(['hello', 'howdy']);
expect(appendWorld(greetings)).to.be.instanceOf(List);
// List [ 'hello world', 'howdy world' ]
});
it('should return an Immutable List after R.filter operation', () => {
const greetings = List(['hello', 'howdy']);
const helloFilter = R.filter(R.equals('hello'));
expect(helloFilter(greetings)).to.be.instanceOf(List);
// List [ 'hello ' ]
});
});
describe('ramda using Immutable.Map and lense apis for CRUD interplay', () => {
const todos = {
'Todo1': {
title: 'Learn Ramda',
value: 'Completed',
notes: [
'sequence documentation appears complex?',
'traverse documentation also complex?'
]
},
'Todo2': {
title: 'Learn Ramda-Fantasy',
value: 'In Progress'
}
};
it('should work with just vanilla Immutable API', () => {
const iMap = Map(todos);
assert(R.equals('Learn Ramda', iMap.get('Todo1').title));
assert(R.equals('Completed', iMap.get('Todo1').value));
});
it('should work with vanilla Ramda Lenses', () => {
const title = lensProp('title');
expect(view(title, todos.Todo1)).to.equal('Learn Ramda');
});
it('should work mixing Immutable Map with Ramda Lenses', () => {
const iMap = Map(todos);
const title = lensProp('title');
expect(view(title, iMap.get('Todo1'))).to.equal('Learn Ramda');
});
it('should work using composition with vanilla Ramda Lenses', () => {
const todo1 = lensProp('Todo1');
const first = lensIndex(0);
const notes = lensProp('notes');
const title = lensProp('title');
// note this compose appears to be working back to front...
const firstNote = R.compose(todo1, notes, first);
expect(view(firstNote, todos)).to.equal('sequence documentation appears complex?');
});
it('should work using composition with ImmutableJs and Ramda Lenses', () => {
const iMap = Map(todos);
const todo1 = iMap.get('Todo1');
const first = lensIndex(0);
const notes = lensProp('notes');
const title = lensProp('title');
const firstNote = R.compose(notes, first);
expect(view(firstNote, todo1)).to.equal('sequence documentation appears complex?');
});
it('should work using vanilla Ramda Lenses to update data values', () => {
const todo1 = lensProp('Todo1');
const title = lensProp('title');
const todo1TitleView = R.compose(todo1, title);
expect(view(todo1TitleView, todos)).to.equal('Learn Ramda');
const result = over(todo1TitleView, R.toUpper, todos);
// result is mutated
expect(result.Todo1.title).to.equal('LEARN RAMDA');
// original is unaffected
expect(todos.Todo1.title).to.equal('Learn Ramda');
});
});
describe.only('ramda using Immutable.seq', () => {
it('should be lazy, vanilla sample only', () => {
var oddSquares = Immutable.Seq.of(1, 2, 3, 4, 5, 6, 7, 8)
.filter(x => x % 2).map(x => x * x);
expect(oddSquares.toJS()).to.eql([1, 9, 25, 49]);
});
it('should see that Seq() is lazy', () => {
const range = R.range(1, 1000);
let numberOfOperations = 0;
let powerOfTwo = Immutable.Seq.of(...range).map((num) => {
numberOfOperations++;
return num * 2;
});
expect(numberOfOperations).to.equal(0);
powerOfTwo.take(10).toArray(); // compute total lazily
expect(numberOfOperations).to.equal(10);
});
it('should see that Seq() is lazy when applied over R.map', () => {
const range = R.range(1, 1000);
let numberOfOperations = 0;
let powerOfTwoMap = R.map((num) => {
numberOfOperations++;
return num * 2;
});
const powerOfTwo = powerOfTwoMap(Immutable.Seq.of(...range));
expect(numberOfOperations).to.equal(0);
powerOfTwo.take(10).toArray(); // compute total lazily
expect(numberOfOperations).to.equal(10);
});
it('should not produce an overflow with infinite Range()', () => {
let powerOfTwoRange = Immutable.Range(1, Infinity);
expect(powerOfTwoRange.size).to.equal(Infinity);
const first1000Powers = powerOfTwoRange
.take(1000)
.map(n => n * 2);
expect(first1000Powers.size).to.equal(1000);
});
it('should not produce an overflow with infinite Range() using Ramda take and map', () => {
let powerOfTwoRange = Immutable.Range(1, Infinity);
expect(powerOfTwoRange.size).to.equal(Infinity);
const take1000AndDouble = R.compose(R.map(R.multiply(2)), R.take(1000));
const first1000Powers = take1000AndDouble(powerOfTwoRange);
expect(first1000Powers.size).to.equal(1000);
});
});
});
Hope this is enough to get users feeling confident they can go away and explore further.
@arcseldon:
I would be happy to take a look at FRP section for
RxJs. Please leave that with me, if I get some time tomorrow then I will write something.
Great! I'd love to see this manual get going again
As for
lodash, would argue thatlodashis largely a competing alternative toRamdarather than complimentary
I certainly wouldn't expect people to use them together, but this chapter was supposed to show both how Ramda interacts with other libraries and how it compares and contrasts with them. For instance, while mori and Immutable serve similar roles, Ramda will probably never be able to interact well with mori (ironically because of its more more FP design.) I would like to see a section explaining how and why this is so.
Also with regards to the Ramda and Ramda-Lense libraries, I am getting confused why Ramda contains some of those - lensProp, lensIndex etc ?
The lens library was pulled out recently to become a playground for working out more advanced Lens-related types. There's no consensus about whether this would eventually involve removing the lens functionality from Ramda proper, but either way we're nowhere near that point now. Functions like lensProp and lensIndex are simply useful sugar over the Lens function, easier to use than R.lens(R.prop(name), R.assoc(name)) or R.lens(R.nth(n), R.update(n)). This is even more true for lensPath
@CrossEye - thank you for this feedback. The explanation on lens related api was very helpful to understanding context / history.
Ok, I am going to take a look at RxJs with Ramda.
Ok, well I did a little research, and to be honest, with regards to RxJs I think I can sum up here the essentials to know about with respect to Ramda and RxJs interplay. I shall start by describing RxJs a little, for those unfamilar with the library, and then move onto my thoughts on how Ramda can be used to good effect.
However, the overall message is this - RxJs shines when dealing with asynchonous, many events (time), whereas Ramda is happy in a synchronous context (space). By that I mean there are many "operators" for buffering etc in RxJs that have no equivalents in Ramda, such as debounce, throttle etc. So just bear that in mind with what follows.
RxJs Intro (skip this if already familiar)
RxJs primarily treats events as collections. Imagine clicking a button several times, well you can consider each click as an item in a list. Except rather than a simple array in Js (being space), we are dealing with events over time.
RxJs comes packed with operators that permit manipulation of sets of events. And remember we are doing this with asynchronous evented sources. RxJs is really about Observables (yes, your intuition is right - think observable pattern, it is sort of similar - playing around with push vs pull and so on).
If you are familiar with Promises, you might be wondering when you would use one over the other. Both promises and Observables are built to solve problems around async (avoid 'callback hell'). However, whereas promises deal with read-only view to a single future value, not lazy (by the time
you have a promise, it's on its way to being resolved). Also promises are uncancellable - your promise will resolve or reject, and only once.
Observable
Types of async in modern web apps:
Really promises only make sense for Ajax (single event). Observables are great for things like a multiplexed websocket
Observable creation helpers in RxJS
it is important to know you can construct new observables out of existing observables, you can join, merge etc etc. It is all very, very cool stuff.
What are 'operators'?
They are methods on Observale that allow you to compose new observables.
let result = source.myOperator();
result is an Observable, that when subscribed to, subscribes to source,
then transforms its values in some way and emits them.
Important distinction from arrays:
simple operators:
map, filter, reduce, first, last, single, elementAt, toArray, isEmpty,
take, skip, startWith, etc etc
merging and joining operators:
merge, mergeMap, concat, concatMap, switch, switchMap, combineLatest,
withLatestFrom, zip, forkJoin, expand
splitting and grouping operators:
groupBy, window, partition
buffering strategies:
buffer, throttle, debounce, sample
Ok, so how on earth can Ramda be of help here?
Great question, I am pleased you asked...
I see only one really useful use case - and I will demonstrate this below with code. However, note too that you could have a Ramda pipeline be the "source" for a RxJs Observable - I just don't really see why you would realistically wish to break out into an asynchronous scenario like that, but it is entirely possible. For instance, you could take the results of a Ramda pipeline, source them through a RxJs Observable, and then fork those results according to different needs, each of which gets fired off to a RabbitMQ queue etc. Again, I have no use case to date that would warrant this, but it is entirely possible.
Rather, what I am focusing on is using Ramda, instead of the RxJs library fluent method chains (operators) to provide Point free, curried ways to manipulate with the events as they fired from the RxJs Observable. This will make a lot more sense by reading the following code. Have tried to provide a little vanilla unit testing too, so you can see these libraries in isolation and then together. There is a unit test to illustrate a synchronous array scenario so you can see just how similar it all appears to working with a plain old array.
'use strict';
/*eslint no-unused-expressions:0*/
const chai = require('chai'),
expect = chai.expect,
Q = require('q'),
R = require('ramda'),
Rx = require('rx');
describe('rxjs with ramda', function () {
this.timeout(2000);
describe('basics with ramda featuring as FP library to the created RxJs Stream(s)', function () {
it('should do map, reduce, filter with vanilla array', function () {
var source = ['1', '1', 'foo', '2', '3', '5', 'bar', '8', '13'];
var result = source
.map(x => parseInt(x))
.filter(x => !isNaN(x))
.reduce((x, y) => x + y);
expect(result).to.equal(33);
});
it('should do map, reduce, filter with vanilla Rx', function (done) {
const source = Rx.Observable.interval(100).take(9)
.map(i => ['1', '1', 'foo', '2', '3', '5', 'bar', '8', '13'][i]);
const result = source
.map(x => parseInt(x))
.filter(x => !isNaN(x))
.reduce((x, y) => x + y);
result.subscribe(x => {
expect(x).to.equal(33);
done();
});
});
it('should do map, reduce, filter with Rx and Ramda', function (done) {
const source = Rx.Observable.interval(100).take(9)
.map(i => ['1', '1', 'foo', '2', '3', '5', 'bar', '8', '13'][i]);
const isNotNaN = R.compose(R.not, R.curry(isNaN));
const result = R.pipe(
R.map(x => parseInt(x)),
R.filter(isNotNaN),
R.reduce(R.add, 0)
)(source);
result.subscribe(x => {
console.log(`x: ${x}`);
expect(x).to.equal(33);
done();
});
});
it('should do map, reduce, filter with Rx and Ramda and multiple observables (streams)', function (done) {
const source = Rx.Observable.interval(100).take(9)
.map(i => ['1', '1', 'foo', '2', '3', '5', 'bar', '8', '13'][i]);
const isNotNaN = R.compose(R.not, R.curry(isNaN));
const baseStream = R.pipe(
R.map(x => parseInt(x)),
R.filter(isNotNaN)
)(source);
const addStream = R.reduce(R.add, 0)(baseStream);
const productStream = R.reduce(R.multiply, 1)(baseStream);
const addSubscription = function () {
const deferred = Q.defer();
addStream.subscribe(x => {
console.log(`x: ${x}`);
expect(x).to.equal(33);
return deferred.resolve();
});
return deferred.promise;
};
const productSubscription = function () {
const deferred = Q.defer();
productStream.subscribe(x => {
console.log(`x: ${x}`);
expect(x).to.equal(3120);
return deferred.resolve();
});
return deferred.promise;
};
Q.all([addSubscription(), productSubscription()])
.then(function () {
done();
});
});
});
});
Again, hope this gives others inspiration to take this further, offer their own insights or just get a sense of what RxJs is if they have never used it before.
Any comments most welcome.
@arcseldon:
Thanks for all this research.
I agree that this is the crux of how the libraries would likely work together. Fluent method chains from RxJs might become piped or composed functions in Ramda. Depending on how generic these were, they might or might not help with readability.
One nit:
R.map(x => parseInt(x))
could be
R.map(parseIInt)
(One advantage of not passing along indices.)
@CrossEye - thanks for the feedback.
Re. parseInt, I genuinely thought I had tried that, only to discover it failed! In rather the same way that for example you can't run a callback with just console.log, but have to wrap in a seemingly redundant outer function.
However, in this case, you are right and I see my mistake:
it.only('should work with parseInt', function () {
const a = ['1'];
// this works...
const b = R.map(parseInt)(a);
console.log(b);
// doesn't work...
const c = R.pipe(
R.map(R.curry(parseInt))
)(a);
console.log(c);
});
I had mistakenly put parseInt inside a R.curry, and then of course it is expecting two args as the signature is: parseInt(string, radix);
@CrossEye - wished to add one point here:
I agree that this is the crux of how the libraries would likely work together. Fluent method chains from RxJs might become piped or composed functions in Ramda. Depending on how generic these were, they might or might not help with readability.
We are in agreement of course that this is where Ramda may aid readability. But one consideration to bear in mind, is that the point-free style doesn't buy you anything here, in the same way that you can ordinarily compose and prime a new function but delay execution until you provide the final argument..
This is because RxJs is inherently lazy, so nothing actually happens until you subscribe
In my last example above, the line:
const addStream = R.reduce(R.add, 0)(baseStream);
sort of looks at odds with how we ordinary build our functions in Ramda, where we prefer to do something like:
const addStream = R.reduce(R.add, 0);
and defer the execution until it is actually used, eg.:
addStream(baseStream).subscribe(x => {...
But in fact, because of the lazy nature of RxJs, and the fact that nothing gets read until subscribe is actually invoked, then the more idiomatic way to do this in RxJs is to eagerly provide the source / stream, ie:
const addStream = R.reduce(R.add, 0)(baseStream);
Then trigger it with:
addStream.subscribe(x => {...
Think this point should be explicitly stated, although end-result is of course, it makes no difference at runtime.
@arcseldon: That is massively helpful to me, thanks a lot! :+1:
@dalgard - most welcome, appreciate the feedback.
Have added a section on Ramda and Ramda-Fantasy interplay here
Yes, please!
closing for inactivity; reopen if needed
As for
lodash, would argue thatlodashis largely a competing alternative toRamdarather than complimentary - so the concept of talking further about how they work with Ramda is somewhat of a non sequitur.
I came from Lodash (FP) to Ramda, and sort of as a result did end up still using them together. I mostly switched over by now. Two Lodash functions I've kept using:
_.size: a 'truthy' checker able to regard {} and [] as falsy_.assignIn: more representative of the differences between Ramda (FP) and regular Lodash, I use this for the use-case of batch-assigning to a class instance (_.assignIn(this, { myvar1, myvar2, ... })). With Angular 2 components as classes, I needed OOP-style mutation here, whereas Object.assign() somehow failed on this. (Unfortunately, TypeScript still wants to know about all the class properties separately, at which point you might just as well assign them separately, so whether this batch assignment is still adding value in this use-case is up in the air...)
_.size: a 'truthy' checker able to regard{}and[]as falsy
isEmpty might be useful in such cases. :)
assignIn is not a good fit for Ramda, of course, where we make a point of never modifying user data.
@davidchambers: thanks. :)
@CrossEye: exactly; I suppose this type of difference in goals is where libraries may be found to complement one another. Not that I'm a big fan of mutation, or I wouldn't have switched over, but if one for some reason had to, well, then that could be one direction to look.
FYI, I'm doing my own version of this. I've written a small series called "Ramda Chops" in the past on https://rwp.im, but I'm now planning out https://ramda.guide.
If anyone has any opinions or feedback, feel free to drop it on the announcement here: https://dev.to/rpearce/working-on-the-ramda-ramp-up-guide-1km4.
@rpearce: As the Ramda team has dropped the ball on this, I'm very glad to see your efforts.
I'm surprised I haven't used Sanctuary (I use https://crocks.dev a lot).
Well, I'm committed to this ramda adventure, so I can be sure to add Sanctuary into the mix toward the end.
@davidchambers Is there a tl;dr you can point me to on those interactive REPL code examples, or is it all custom? Those are _excellent_.
Most helpful comment
Ok, well I did a little research, and to be honest, with regards to RxJs I think I can sum up here the essentials to know about with respect to Ramda and RxJs interplay. I shall start by describing RxJs a little, for those unfamilar with the library, and then move onto my thoughts on how Ramda can be used to good effect.
However, the overall message is this - RxJs shines when dealing with asynchonous, many events (time), whereas Ramda is happy in a synchronous context (space). By that I mean there are many "operators" for buffering etc in RxJs that have no equivalents in Ramda, such as
debounce,throttleetc. So just bear that in mind with what follows.RxJs Intro (skip this if already familiar)
RxJs primarily treats events as collections. Imagine clicking a button several times, well you can consider each click as an item in a list. Except rather than a simple array in Js (being space), we are dealing with events over time.
RxJs comes packed with
operatorsthat permit manipulation of sets of events. And remember we are doing this with asynchronous evented sources. RxJs is really about Observables (yes, your intuition is right - think observable pattern, it is sort of similar - playing around with push vs pull and so on).If you are familiar with Promises, you might be wondering when you would use one over the other. Both promises and Observables are built to solve problems around async (avoid 'callback hell'). However, whereas promises deal with read-only view to a single future value, not lazy (by the time
you have a promise, it's on its way to being resolved). Also promises are uncancellable - your promise will resolve or reject, and only once.
Observable
Types of async in modern web apps:
Really promises only make sense for Ajax (single event). Observables are great for things like a multiplexed websocket
Observable creation helpers in RxJS
it is important to know you can construct new observables out of existing observables, you can join, merge etc etc. It is all very, very cool stuff.
What are 'operators'?
They are methods on Observale that allow you to compose new observables.
let result = source.myOperator();result is an Observable, that when subscribed to, subscribes to source,
then transforms its values in some way and emits them.
Important distinction from arrays:
to the next value in the set. This is different from array operators (think map and filter) which will
process the entire array at each step
simple operators:
map, filter, reduce, first, last, single, elementAt, toArray, isEmpty,
take, skip, startWith, etc etc
merging and joining operators:
merge, mergeMap, concat, concatMap, switch, switchMap, combineLatest,
withLatestFrom, zip, forkJoin, expand
splitting and grouping operators:
groupBy, window, partition
buffering strategies:
buffer, throttle, debounce, sample
Ok, so how on earth can Ramda be of help here?
Great question, I am pleased you asked...
I see only one really useful use case - and I will demonstrate this below with code. However, note too that you could have a Ramda pipeline be the "source" for a RxJs Observable - I just don't really see why you would realistically wish to break out into an asynchronous scenario like that, but it is entirely possible. For instance, you could take the results of a Ramda pipeline, source them through a RxJs Observable, and then fork those results according to different needs, each of which gets fired off to a RabbitMQ queue etc. Again, I have no use case to date that would warrant this, but it is entirely possible.
Rather, what I am focusing on is using Ramda, instead of the RxJs library fluent method chains (operators) to provide Point free, curried ways to manipulate with the events as they fired from the RxJs Observable. This will make a lot more sense by reading the following code. Have tried to provide a little vanilla unit testing too, so you can see these libraries in isolation and then together. There is a unit test to illustrate a synchronous array scenario so you can see just how similar it all appears to working with a plain old array.
Again, hope this gives others inspiration to take this further, offer their own insights or just get a sense of what RxJs is if they have never used it before.
Any comments most welcome.