Since they are used in example code, would it be possible to add definitions of Just and Maybe to the documentation?
Alternatively, maybe those examples could be replaced with plain JS types or Ramda functions?
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 Ord, Lens, Filterable, etc. Since we're using Maybe and its Just and Nothing subtypes in examples, they should also be documented. This is not something I think is hard to do; it's simply never bubbled to the top of the list.
@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)?
I'll also open any other documentation-related issues in that repo.
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 (R.sequence(R.of, [[1, 2], [3, 4], [5, 6]]) == [[1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]]) helped me finally see what I could do with it.
Are there issues with having simple examples like that following the current ones?
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 traverse is just sequence with a mapping function thrown in.)
Awesome! I'll be able to play around with them now. :smile:
Unfortunately, it's 1am here so my play time will need to be later.
Examples should depict common usage. R.sequence and R.traverse are primarily useful for getting from an _A of B_ to a _B of A_. Transforming a list of lists into a list of lists doesn't exemplify this inversion.
Examples should depict common usage.
R.sequenceandR.traverseare primarily useful for getting from an _A of B_ to a _B of A_. Transforming a list of lists into a list of lists doesn't exemplify this inversion.
I think this is the problem. The main usage of, say, sequence is probably for something like the first example1 in the documentation:
R.sequence(Maybe.of, [Just(1), Just(2), Just(3)]); //=> Just([1, 2, 3])
But that is likely to confuse someone who's never seen Maybe. But examples with the only other type we can comfortably use, lists, don't really show what it's all about.
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
But that is likely to confuse someone who's never seen Maybe.
In that case one is probably not going to find the function useful.
To complete the circle: would it be possible to define Just and Maybe somewhere that is linked from the examples? :stuck_out_tongue_winking_eye:
I would need some links to where I could learn this stuff before I could offer better suggestions.
Sanctuary's S.Just and S.Nothing and ramda-fantasy's Maybe.Just and Maybe.Nothing are defined in the repl
I assume these are what you mean:
Definitely seems like useful reading.
Any chance that descriptions of how concepts like Maybe, Just, and Nothing integrates with Ramda will be added to the wiki page on Type Signatures?
This is what I was originally seeking when creating issue https://github.com/ramda/ramda/issues/1627.
@dalgard - your request is a good one. In meantime, have you taken a look at the ramda-fantasy docs, for example Maybe Here, ramda and Maybe are being used freely together.
@arcseldon: Yeah, thanks. I actually understand the concept of Maybe already – what I'd like to know more about is Ramdas relationship with the types.
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 undefined is a real problem (isn't it?)...
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 src/Maybe.js shows
function Maybe(x) {
return x == null ? _nothing : Maybe.Just(x);
}
So you can use that constructor,
Installed into REPL context:
- [email protected] as R
- [email protected] as RF
> RF.Maybe(R.find((a) => a > 2, [1, 1, 1, 1, 1]))
_Nothing {}
> RF.Maybe(R.find((a) => a > 2, [1, 1, 1, 1, 3]))
_Just { value: 3 }
data.maybe has Maybe.fromNullable for this purpose.
@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.
@dalgard - At least we are in a boat, and not drowning :smile:
@raine - thanks for pointing out the Constructor usage! Wow, I think we have just written the requested documentation.. :smile:
@raine: What about the composition example that @arcseldon just posted?
> const safeFind = pipe(find, Maybe)
> safeFind(gt(__, 2), [1,1,1])
_Nothing {}
> safeFind(gt(__, 2), [1,1,3])
_Just { value: 3 }
here's a quick example of safeFind:
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 defaultTo or tryCatch
@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)
compose(map(f), map(g)) ≍ map(compose(f, g))
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:
compose(map(f), map(g)) ≍ map(compose(f, g))
Well, lets try it out. What we'd expect is:
compose(map(decrement), map(double), map(increment);
to be the same as:
map(compose(decrement, double, increment));
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:
The specific types are ones that have shown themselves to be quite useful for many developers over a long time
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?
I'd be grateful if others could offer up whether they actively program with implementations of fantasy land specs
I would too... :)
Stob put it well:
Monad property 4: Monads rarely throw exceptions. Instead they prefer to pretend nothing happened and pass the Empty Monad down the chain. This improves the level of surprise when you get to the end of a long calculation and find that, instead of a nice diagnostic stack trace or the actual answer, you have the computing science equivalent of a belch. But never mind. Admire the smooth, alimentary flow of the functional pipeline that delivered it!
:smile: priceless.
But never mind. Admire the smooth, alimentary flow of the functional pipeline that delivered it!
Yep, the way it all works out is seductive. There is some good commentary here on Railway oriented programming, which i am guessing many readers of this issue have already seen / read. Despite the excellent explanations, and extolling the benefits, I am still not entirely convinced.
Tony Hoare, the inventor of null references, also describes it as the Billion Dollar Mistake - and it would be great to be convinced there is indeed a better way...
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?
Here's some real-world code which gracefully handles three possible failure conditions:
pipe([get(String, 'more_info'),
map(pipe([replace('/*-secure-', ''),
replace('-secure-*/', ''),
replace(/\\(?=')/g, ''),
replace(/\\x([0-9A-F]{2})/g,
(_, s) => String.fromCharCode(parseInt(s, 16)))])),
chain(parseJson),
chain(get(String, 'transactionAdditionalInfo')),
fromMaybe(''),
cheerio.load],
raw);
No conditional logic in sight!
Here's the rough imperative equivalent:
if (typeof raw.more_info === 'string') {
const normalized = raw.more_info.replace(…).replace(…).replace(…).replace(…);
let parsed;
let valid;
try {
parsed = JSON.parse(normalized);
valid = true;
} catch (err) {
valid = false;
}
if (valid) {
if (typeof parsed.transactionAdditionalInfo === 'string') {
const $ = cheerio.load(parsed.transactionAdditionalInfo);
// Do something with $...
}
}
}
// Handle failure...
@davidchambers - thanks for this example, which on face value is compelling.
I'm intrigued enough to try out sanctuary for a few weeks in earnest, and train myself to work with the types, and reach a point where I can make an informed decision for myself. Am picking up Haskell again after a year's absence, so this is all nicely aligned & comes at a good time. Hope you won't mind me asking you a question here or there along the way! Your talk on functional geekery where you consider how to handle invalid input (00:48:00 onwards) - also in regards to testing - makes alot of sense.
Hope you won't mind me asking you a question here or there along the way!
Not at all! I don't have all the answers, though. :)
Quick update here:
Am a week into this journey, and I would say I am certainly encouraged so far. Must have written a couple hundred test cases exploring usage, and how the data types interact with one another.
In fact, am encouraged to the point where I'd be in favour of enhancing Ramda to provide default support for some data types for its API functions. The main barrier is familiarity with the various constructs for manipulating / working with the data types. Once that barrier starts to disappear usage becomes quite addictive, and it is frustrating to return to plain old javascript equivalents that don't have baked in support.
Taking this simple example using Sanctuary:
const S = require('sanctuary');
...
it('should handle success and failure scenarios for parseFloat', () => {
S.parseFloat('xxx');
// Nothing()
S.parseFloat('12.34');
// Just(12.34)
expect(S.Just('xxx').chain(S.parseFloat)).to.eql(S.Nothing());
expect(S.Just('12.34').chain(S.parseFloat)).to.eql(S.Just(12.34));
expect(map(add(3))(S.Just(1))).to.eql(S.Just(4));
});
The fact that S.parseFloat bakes in the Maybe support does greatly assist "fluent api usage".
Am starting to see the argument for default implementations, and even consider collapsing ramda-fantasy package into ramda core. But I shall hold judgement here for a few more weeks in case I discover any late issues.
On that note, I'd like to start using some of this in Production (commercially at work).
Are there any reservations around ramda-fantasy and prd usage? Should I opt for Sanctuary / Folktale (in the near term at least)? The code and test coverage looks comprehensive in ramda fantasy, but I would prefer to mitigate any risks where possible - and need some assurances it is battle tested.
@davidchambers - take it Plaid is using Sanctuary for Production atm, or is it still in incubation?
Am starting to see the argument for default implementations, and even consider collapsing ramda-fantasy package into ramda core.
There is a real argument to be made for that. (Just ask @davidchambers! :smile:) 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 https://github.com/ramda/ramda/pull/1218#issuecomment-115011615:
Sanctuary embraces correctness in a way that Ramda cannot, given Ramda's goal of remaining approachable to JavaScript programmers with no FP experience. This is a worthwhile goal, as we can't expect such programmers to jump directly to PureScript. This is a smoother upgrade path:
Vanilla JavaScript → Underscore/lodash → Ramda → Sanctuary → PureScript
Remaining approachable involves especially returning from our functions something immediately useful to work-a-day programmers. head([1, 2, 3]) returns 1. I can just use that directly, and I don't have to worry about anyone's odd container type to do so. If it returned Just(1), then it is definitely safer. The same code could handle the undefined equivalent of head([]) returning Nothing(), but it's far less approachable. map? chain? Huh?
Are there any reservations around ramda-fantasy and prd usage? Should I opt for Sanctuary / Folktale (in the near term at least)?
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 Maybe wrappers around potentially unsafe calls, then I would definitely go with Sanctuary.
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 (Reader, IO, Future/Task, Validator) then you should consider one of these libraries.
@CrossEye - cannot argue against the upgrade path analogy.
head([1, 2, 3]) returns 1 - yep, that is already starting to look suspect now.
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 IO or Reader at this point, but Future I could see being useful on occasion. I really like the pure way in which nothing happens until you call fork on them - that to me is an advantage over vanilla promise usage.
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() ?
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.
I think many of us here find Promises to be too unlawful to be useful. I know that I personally prefer to use Futures or Tasks instead.
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() ?
Yuri Takhteyev, who introduced the function, wrote about it in December, 2014.
Thank you, the explanation regarding composeP / pipeP was exactly what I was looking for, as well as getting the context / history too.
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:
I know that I personally prefer to use Futures or Tasks instead.
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 :smile:
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).
@davidchambers - take it Plaid is using Sanctuary for Production atm, or is it still in incubation?
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.
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.
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 :see_no_evil:
I'd love to add a Task type, or receive a pull request for one
Not from me you wouldn't :smile: 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...
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 require('data.task') is a significant barrier to entry. Were S.Task to exist there would be less opposition to using tasks. :)
Which javascript Future implementation are you using? (or is that Folktale's renamed data.Task implementation you are referencing?)
Until recently, I always used data.Task. I now sometimes use R-F's Future.
I was hoping we were eating our own dog food :smile:
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 map, ap, chain, or whatever as well. So choosing one should have to do with other things besides Ramda.

@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 Maybe and Either. I imagine it would be straightforward. But I've never spent any time on that integration.
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 preamble of the embed. The preamble property just has code that you want to load before the user-editable portion. It's currently used to get R into scope for example.
To follow up, this line would just need the above added to it.
@tolmasky: Perfect. Thank you very much!
@Jeff-Tian , you can Open in REPL instead of "Run it here", Ramda's REPL import ramda-fantasy & Sanctuary which contain Maybe and Just.
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:
ramda-fantasy's repository, the project is no longer maintained. Is there some alternative Maybe/Either that is preferred? Seems iffy to add a library that is close to deprecation, but happy to do it.ramda-fantasy, could we perhaps make a minor change to ramda-fantasy to make ramda a peerDependencies instead of dependencies? As is, ramda-fantasy essentially relies on bespoke to deduping to not include ramda twice, whereas by listing ramda as a peerDependency, the deduping intention would be explicit.Neither of these are deal breakers, and it works as is, but the second point would improve performance for example.
@tolmasky:
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: https://github.com/ramda/ramda.github.io/pull/228
@CrossEye I'll take a look at the tiddlywiki thing.
@tolmasky:
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.
Most helpful comment
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?