New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define Just/Maybe in doc examples #1610
Comments
I think the changes to the repl should take care of this, next time the docs are built. In that case we will have to be sure we fully qualify which Just/Nothing we mean in the docs |
This is on my list of things to do in the documentation. I think we should document all external types used, such as @MattMS: Is this something you'd care to take a crack at? The documentation is maintained in a separate project if you are. |
I'd love to help out with the documentation but my only hesitation is not being familiar enough with functional programming and the libraries described to do it properly. Can provide any links to resources I should check out (like the types described)? |
Unfortunately the "plain JS" examples are not as useful as the ADT examples, which is why we chose to use those for illustration. For example(s): http://goo.gl/ymj54P |
Actually the last example ( Are there issues with having simple examples like that following the current ones? |
no problem at all. let's make it clearer if we can. (btw |
Awesome! I'll be able to play around with them now. 😄 Unfortunately, it's 1am here so my play time will need to be later. |
Examples should depict common usage. |
I think this is the problem. The main usage of, say,
But that is likely to confuse someone who's never seen One suggestion floated recently would be for there to be two levels of documentation for some or all functions, a quick gloss that explains the type signature and gives a brief example or two and a separate, much more in-depth explanation. While that sounds great, I'm not sure how to accomplish it, or even if it would really solve this problem. 1 The second example actually seems to be wrong. (http://goo.gl/ZzlJw9 |
In that case one is probably not going to find the function useful. |
To complete the circle: would it be possible to define I would need some links to where I could learn this stuff before I could offer better suggestions. |
Sanctuary's |
I assume these are what you mean: Definitely seems like useful reading. |
Any chance that descriptions of how concepts like This is what I was originally seeking when creating issue #1627. |
@arcseldon: Yeah, thanks. I actually understand the concept of I'd like an explicit account of:
|
@dalgard - how are you currently trying to use them ? Can you offer some code example. |
That's the thing, I'd like to know the answers to my questions above before I begin using them in a serious way. |
@dalgard - I sympathise completely - currently things are not clear for all the aforementioned reasons in this and previous issues. Let's use Maybe as an example, to illustrate my take on things. I am in the same boat as you. Understand the concepts, have little practical experience applying the fantasy types to daily code in JS - have done so in Scala quite a bit using similar API. Here we shall ignore Sanctuary completely. Lets consider ramda-fantasy as our extension NPM package for ramda in order to get some types such as Maybe. In fact, lets just pick on Maybe and use it with say (randomly choosing a useful everyday function from Ramda) R.find So simple, vanilla example: 'use strict';
const R = require('ramda');
const numbersA = [1, 2, 3, 4, 5];
const numbersB = [1, 1, 1, 1, 1];
const greaterThanTwo = (a) => a > 2;
const resultA = R.find(greaterThanTwo, numbersA);
console.log(resultA);
//=> 3
const resultB = R.find(greaterThanTwo, numbersB);
console.log(resultB);
//=> undefined Ok, so all good, but that We can introduce Maybe to solve this one as follows, but R.find doesn't know anything about Maybe so lets see if we can bolt it onto the end using composition. First attempt: const R = require('ramda'),
RF = require('ramda-fantasy'),
Maybe = RF.Maybe,
Just = Maybe.Just,
Nothing = Maybe.Nothing;
const numbersA = [1, 2, 3, 4, 5];
const numbersB = [1, 1, 1, 1, 1];
const greaterThanTwo = (a) => a > 2;
const maybeFound = R.compose(Maybe.of, R.find(greaterThanTwo));
const resultA = maybeFound(numbersA).getOrElse(2);
console.log(resultA);
//=> 3
const resultB = maybeFound(numbersB).getOrElse(2);
console.log(resultB);
//=> undefined Close, but no cigar. Notice that unfortunately we can't just compose with Maybe.of because that would result in a Just(undefined) instead of a Nothing when no result was found... So, lets bin using Ramda find, and roll our own with baked in support for Maybe. 'use strict';
const R = require('ramda'),
RF = require('ramda-fantasy'),
Maybe = RF.Maybe,
Just = Maybe.Just,
Nothing = Maybe.Nothing;
// Returns Maybe.Just(x) if some `x` passes the predicate test
// Otherwise returns Maybe.Nothing()
function find(predicate, xs) {
return R.reduce(function(result, x) {
return result.isJust() ? result :
predicate(x) ? Just(x) : Nothing()
}, Nothing());
}
const numbersA = [1, 2, 3, 4, 5];
const numbersB = [1, 1, 1, 1, 1];
const greaterThanTwo = (a) => a > 2;
const maybeFound = find(greaterThanTwo);
const resultA = maybeFound(numbersA).getOrElse(2);
console.log(resultA);
//=> 3
const resultB = maybeFound(numbersB).getOrElse(2);
console.log(resultB);
//=> 2 Ok, that works. There must be a better way right? @CrossEye @buzzdecafe - please can you enlighten us? Or is it that we should roll our own recipes to get the Just / Nothing decision logic in there... |
Quick peek in function Maybe(x) {
return x == null ? _nothing : Maybe.Just(x);
} So you can use that constructor,
|
@arcseldon: Thanks for that. We're definitely in the same boat! @raine: What about the composition example that @arcseldon just posted? Please note that I have a few other points above that I'd love to see answered by the developers, preferably as part of the documentation for Ramda. |
|
here's a quick example of const Just = Maybe.Just;
const Nothing = Maybe.Nothing;
var ones = [1,1,1,1,1,1,1];
const safeFind = compose(Maybe, find);
safeFind(gt(__, 0), ones); //=> Just(1)
safeFind(gt(__, 1), ones); //=> Nothing |
too late, @raine beat me to an almost identical example |
@buzzdecafe - yep, but thank you anyhow - and now we have a Maybe in play, we can just map our way through further compositions etc. |
right, the problem is once you are inside that functor you are committed to it. You could stay at a more primitive level by composing with |
@buzzdecafe - Right. Seeing as we have made great inroads in this issue towards fully exploring and understanding Ramda and its interplay with Maybe (in this case from ramda-fantasy) - I wanted to go a little further by way of examples, and pull in some valuable commentary from another issue - do tend to plagiarize the core team alot when it comes to explaining this stuff - on this occasion from @CrossEye.. Stated as is: Although what is specified by FantasyLand is what Haskell calls typeclasses the basic notion is simply one of algebraic types, types defined by a collection of functions/methods that apply to them and laws about how those functions interact. The specific types are ones that have shown themselves to be quite useful for many developers over a long time. They are generally fairly abstract, but the laws specifying their interaction make them quite useful. For instance, Functor defines map, and the composition law of Functor says that (in Ramda terms)
A number of libraries create types that implement some of these algebraic specifications, including Folktale, RamdaFantasy, and Sanctuary. Ramda itself does not try to implement any of these specifications. But many of its functions delegate to named methods on the relevant objects, and this include almost all of the functions specified by the various algebraic types of FantasyLand. So taking @buzzdecafe example of safeFind, lets see what happens if we wish to compose a few (contrived in this example) more Ramda functions off the back of the Maybe result. The question is, how do we continue the interplay between vanilla Ramda and Ramda-Fantasy. Well, my first take on this is we just map each of the functions along the pipeline, which is like opening up the Maybe box, applying the needed function on the contents, and then wrapping backup in a Maybe box. Note in this example, there isn't the possibility of a Nothing decision, it is more about working with Maybe as-is - either passing on the Nothing result unchanged, or else updating the Just value. 'use strict';
const R = require('ramda'),
compose = R.compose,
map = R.map,
find = R.find,
gt = R.gt,
increment = R.inc,
double = R.multiply(2),
decrement = R.dec,
flip = R.flip,
RF = require('ramda-fantasy'),
Maybe = RF.Maybe,
Just = Maybe.Just,
Nothing = Maybe.Nothing;
var ones = [1,1,1,1,1,1,1];
const findGtZero = find(flip(gt)(0));
const findGtOne = find(flip(gt)(1));
const safeFindGtZero = compose(Maybe, findGtZero);
const safeIncrement = map(increment);
const safeDouble = map(double);
const safeDecrement = map(decrement);
const safeFindAndCalcGtZero = compose(safeDecrement, safeDouble, safeIncrement, safeFindGtZero);
safeFindAndCalcGtZero(ones);
//=> Just(3)
const safeFindGtOne = compose(Maybe, findGtOne);
const safeFindAndCalcGtOne = compose(safeDecrement, safeDouble, safeIncrement, safeFindGtOne);
safeFindAndCalcGtOne(ones);
//=> Nothing I don't know about you, but that looks horrid to me, so many intermediary functions or map wrapping going on... Can we do better? Sure, remember this:
Well, lets try it out. What we'd expect is:
to be the same as:
Ok, lets give that a spin: const safeCalc = map(compose(decrement, double, increment));
const safeFindAndCalcGtZero_delta = compose(safeCalc, safeFindGtZero);
safeFindAndCalcGtZero_delta(ones);
//=> Just(3)
const safeFindAndCalcGtOne_delta = compose(safeCalc, safeFindGtOne);
safeFindAndCalcGtOne_delta(ones);
//=> Nothing It worked. Amen. Still not sure I am sold on this style of coding, but going back to what @CrossEye explains:
So perhaps the people have already spoken, and my personal bias is more down to what I am familiar with rather than what is truly useful. I'd be grateful if others could offer up whether they actively program with implementations of fantasy land specs, or whether this stuff (in JavaScript world, where the language doesn't offer baked in support) really is just that - a fantasy? |
There is a real argument to be made for that. (Just ask @davidchambers! 😄) And this has discussed at least as far back as #683. The reason I've been against doing it goes back to the fundamentals of Ramda. David described it very well in #1218 (comment):
Remaining approachable involves especially returning from our functions something immediately useful to work-a-day programmers.
I can't really tell you how battle-tested it is. To me, it's been more of a playground. @TheLudd or @buzzdecafe or others might be able to give you a clearer view. If all you're looking for is the safer versions of Ramda functions, especially But Sanctuary is not attempting to do what R-F or Folktale are doing, which is to supply substantial useful implementation of various common types. So if you want to use some of those ( |
@CrossEye - cannot argue against the upgrade path analogy.
Shall continue to mix ramda and sanctuary when required, should suffice. Thank you also for pointing out that a Fantasy land implementation is still required for some of the other implementations. I am unclear whether I really need For instance, wrapping readFile etc might be one way to solve callback syntax - not really had enough practical experience to comment though. //+ readFile :: String -> Future(Error, String)
const readFile = (filename) => {
return Future((rej, res) => {
fs.readFile(filename, 'utf-8', (err, data) => {
err ? rej(err) : res(data);
});
});
}; with incorporated pipeline creation: //+ fiveLines :: String -> String
const fiveLines = compose(join('\n'), take(5), split('\n'));
//+previewFile :: String -> Future(Error, String)
const previewFile = compose(map(append('...')), map(fiveLines), readFile) Or perhaps to wrap http calls: //+ getJSON :: String -> Object -> Future(Error, JSON)
const getJSON = curry((url, params) => {
return new Future((rej, res) => {
$.getJSON(url, params, res).fail(rej);
});
}); and then invoke: //+ posts :: Object -> Future(Error, [Post])
const posts = compose(map(sortBy('date')), getJSON('/posts'));
posts({}).fork(logError, renderView); (source: Monad a Day 2: Future - Thanks Brian) However, am still a little unclear on its interaction with say bluebird / Q promise libraries for doing more advanced promises manipulation, in parallel etc... would be great to get some examples here. Likewise, i would be grateful if anyone could point out usage of Ramda's composeP - assuming that is just async waterfall, or joining together promises with .then() ? |
I think many of us here find Promises to be too unlawful to be useful. I know that I personally prefer to use
Yuri Takhteyev, who introduced the function, wrote about it in December, 2014. |
Thank you, the explanation regarding Example usage: const getCostWithTaxAsync = R.pipeP(
getItem, // get a promise for the item
R.prop('cost'), // pull out the 'cost' property
R.multiply(1 + TAX_RATE) // multiply it by 1.13
); // returns a promise for the cost with tax Regarding Futures:
Which javascript Future implementation are you using? (or is that Folktale's renamed data.Task implementation you are referencing?) I was hoping we were eating our own dog food 😄 |
Compared with the example above, I do think the composeP and pipeP documentation could be made more approachable. Appreciate it is accurate, but the way the promise propagates itself through the pipe etc doesn't mean every function in the pipe has to be explicitly promise returning. The above example demonstrates nicely that you can just have ordinary Ramda functions anywhere after a Promise returning function is invoked (in same way you can do likewise within a .then() chain using promises directly). |
We've been using Sanctuary heavily in one project, Parse, for the past year. Parse makes great use of Ramda as well. Every Plaid project written in JavaScript uses Ramda. Many use Sanctuary as well, though often in just a handful of places.
This is a function of my time and attention. I'd love to add a Task type, or receive a pull request for one. ;) |
@davidchambers - thank you for your reply. Great, the fact that Ramda and Sanctuary have received heavy usage is reassuring. I was using Ramda version 0.7 in production with a previous Client back in early 2014 - don't like to dwell on such things - sure it was all performant and bug free 🙈
Not from me you wouldn't 😄 but i guess I could always cobble something together based on other libraries at a push.. Seriously, IF I get some time I'd really like to have a go, but not making any commitment here. The workload from new Client is really ramping up now so I am going to have less time available. What are you using at Plaid to fill the gap regarding Fantasy Land data types? Or aren't you using anything unless Sanctuary / Ramda covers it... |
plaid/async-problem makes a compelling case for Task, but right now most of our projects use some flavour of Promise. :\ Funnily enough, I think |
Until recently, I always used data.Task. I now sometimes use R-F's Future.
I don't feel that way at all about Ramda-Fantasy. To me it's mostly been a small learning project, trying to find out what these ADT's are all about. Of course Ramda itself started that way too, but in my mind R-F hasn't come close to making the transition to being a fully formed library the way Ramda did some time back. But I may not have the right impression either, as I haven't been very involved. I've on and off been working on something which is related to the FantasyLand specs, but which is built a very different way. It's quite far from ready, though. But this has taken whatever energy I might have put into R-F. But one of the whole points of Ramda's dispatching mechanism is that Ramda should be able to work well with any compliant implementation of a particular F-L spec. It might work fine with other objects that have a |
@Jeff-Tian , you can Open in REPL instead of "Run it here", Ramda's REPL import ramda-fantasy & Sanctuary which contain Maybe and Just. |
This does make me wonder, though, if it would be easy enough to update our RunKit implementation to include some version of Anyone know how to do this? @ramda/ramda-repl, @ramda/core, @tolmasky? |
You can accomplish this fairly trivially by adding something like this: const { Maybe, Either } = require("ramda-fantasy"); to the |
To follow up, this line would just need the above added to it. |
@tolmasky: Perfect. Thank you very much! |
Thank you @adispring . But most of the time I hope I can run it through RunKit, because REPL uses some CDNs that behaves badly in Shanghai: I know I can use VPN, but if runkit works, then I don't need to bother using it. |
I am getting a pull request ready that adds this feature. However, I had a couple questions before submitting:
Neither of these are deal breakers, and it works as is, but the second point would improve performance for example. |
I can't see that it would hurt to change it to a peer dependency. But I should do a little reading first. I would probably stick with the ramda-fantasy version for now. Most of the Maybe/Either usages we have have been tested with that repo, and they are very simple. It should be easy to change to it later. On a side note, I'm wondering if you've seen any integrations of RunKit with https://tiddlywiki.com/? I'm working on another project, which I was thinking of documenting with TiddlyWiki, but would love to have runnable snippets. I haven't done any research on this, so there may be an obvious answer somewhere. But if you have seen it, could you let me know? |
OK, I have filed a pull request to support this: ramda/ramda.github.io#228 @CrossEye I'll take a look at the tiddlywiki thing. |
Thank you. I will take a look at the PR this weekend. Please don't worry much about TiddlyWiki. I was hoping that you might have already seen such an integration. If not, it's not a big deal. If and when I get to that stage, I'll start looking. |
Hi @MattMS and @Jeff-Tian, this should be live on the site now, so I think we can close if we can confirm it works the way you'd like now? |
Cool! Love it and thanks a ton. |
Closing since related pull request has been merged. |
Since they are used in example code, would it be possible to add definitions of
Just
andMaybe
to the documentation?Alternatively, maybe those examples could be replaced with plain JS types or Ramda functions?
The text was updated successfully, but these errors were encountered: