I've gone pretty deep down this particular FP rabbit hole in JS before. I'm not saying that it shouldn't be in your toolbox, but in my experience currying should not be something you regularly reach for.
Agree & disagree. What you need is a language where all of these features are first class, piping/compose operators, etc. but without this, the code base becomes unidiomatic, hard to read, and difficult for coworkers to understand. Forcing it into JS is where you have a bad time – there are many options for functional languages that compile to JS tho.
> there are many options for functional languages that compile to JS
Arguably, depending on what you're building, the pain of integrating an entirely different language into your stack is greater than the pain of simply using something like Ramda (or, better yet, just writing simple, imperative code). YMMV of course—if the problems you're solving lend themselves naturally to purely functional code, maybe something like Rescript or PureScript may be worth it, but for most projects, I'd guess probably not.
Rescript compiles to really efficient JS. Learning React with ReasonML (Rescripts predecessor) spoiled me, in a bad way. Waiting for TS to compile was really painfull. In Reason it took fractions of a second.
+1 the ReScript compiler is a beast: not only is it fast at generating JS, the JS it generates is optimised for performance[1]. If you are using React, it's better to go for ReScript rather than using Ramda or lodash-fp.
Idiomatic describes the common practices of the ecosystem, without the features (specifically currying, since it's the only one mentioned).
How does a codebase that doesn't use these features become unidiomatic?
Idiomatic JavaScript doesn’t use currying. If you add it to your code base, you are now siloing your project to folks that understand currying which has two problems: 1) hiring Joe Schmo to add/maintain will be harder 2) folks that understand currying will almost certainly be more happy & productive in a language that has currying by default & is idiomatic.
For instance, the audience that sees fp-ts as a necessary, useful library to code in a manner they find most appropriate will find JavaScript/TypeScript ergonomics verbose, hard to read, and require someone with functional programming experience (see the verbosity according to their docs of TypeScript+fp-ts vs. PureScript: https://gcanti.github.io/fp-ts/guides/purescript.html). That audience would be better suited doing compiled option such as the aforementioned PureScript, or js_of_ocaml, ReScript, derw, Idris, GHC.js/whatever the new GHC compiler JS output is, or similar option. These languages will have the idioms of fp-ts without the heavy reliance on singularly library most of the TypeScript community doesn’t understand that bloats their code bases without requiring the safety (someone can just call .value on a Maybe/Option breaking the safety). (This is not a knock against fp-ts which is a marvel in & of itself, but the audience doesn’t line up with the idioms of the average TypeScript project).
In the late 10s I worked at a startup that was not adopting Flow or TypeScript, and pushing FP using Ramda/lodash-fp seemed like a way to encourage devs to separate out data from dataflow and make things into reusable pieces. Instead of some spaghetti imperative code, we could write a bunch of small functions that work on well-known entities, and compose them up to make a procedure. In theory it was better.
When the company finally came around to using TypeScript, devs could once again reason about code and data and relationships between functions without resorting to abstraction, and the FP stuff died a quick death.
FP-style castle of abstractions is just as bad as OO-style castle of abstractions. A "function that returns a function that returns a function that returns a..." is just as bad as an "AbstractFactoryManager that manages an AbstractFactory that produces an Abstract thing..." etc.
Don't write code that is so far removed from the actual business data that it becomes hard to reason about cause and effect.
Abstraction is something else entirely and is usually what you want. Think more like, "things that have provable laws." Eg: concatenating two lists results in a list that is at least as long as the longest input list, for all lists. Concatenation is the operation on lists that is the abstraction... once you have defined it you no longer have to think in terms of loops and building intermediate lists.
It really comes down to finding the right balance and using your tools effectively. I worked at a place at was super OO heavy, multi-layer inheritance, gang-of-4 type OO patterns. And another place that was writing code in a functional manner, little to no classes unless necessary, using FP patterns in the codebase.
Both were codebases were TypeScript. I think I was more effective in the second, FP, codebase than the first. But really it's more about finding the right balance. One of the flaws in either approach is you try to fit a paradigm forcefully: trying to write Java in JS/TS or trying to write Haskell in JS/TS. The TS code will be better if you understand and accept the JS/TS paradigm and apply patterns where they fit elegantly.
Long time ago we already had a large lodash codebase, but I remember liking ramdas api design more. Did a few pocs but we never needed it. We did need a lot of fp though. We were on es6 and constantly merging observables. Lodash was a good supporting library for that, but I think the Ramda version would have been a little cleaner.
I view Ramda as a redflag in most any code base I see it in. Anecdotally, every large codebase I have worked with that heavily used Ramda is generally held together by one very smart engineer. Once that engineer leaves the company, the code base falls apart because no one else can understand it very well and it takes ages to fix bugs or ship new features. So these days I champion against Ramda simply because I think it just takes to much effort for a typical engineer to understand and it is usually at odds with the rest of the companies code.
If you use something like redux toolkit query, it already abstracts that for you using Immer internally. You can just write a simple arrow function or use lodash or underscore or whatever pleases you.
I kind of worked in a place similar to this. But I think having someone who understands engineering as opposed to just functional programming is important. The codebase I worked on was quite functional, written in TypeScript with libraries like fp-ts, io-ts. It did take a while to figure out some of the things, but ultimately I think the engineer who was leading really understood not to go full pure Haskell-in-JS since others on the team weren't hardcore FP programmers.
Sounds like you've worked with people abusing Ramda. It's not that complicated when used as a utility library rather than some hard FP approach to programming...
I've been using Ramda for years and I'm really struggling to see how any of the code in our projects have been complicated by it vs lodash or any other similar utility library.
But then why use Ramda at all then? There's plenty of non dogmatic libs that don't try to get you to curry your functions and spice your parameters and traumatize your children or whatever. Ramda encourages (in its official docs) unreadable code for the sake of style purity...
Because I prefer the FP style, arg orders, name schemes, documentation, and Ramda is just generally very clean and simple. And you absolutely do not need to use currying when using Ramda. It's entirely optional (just pass 2 arguments...). I tried it a couple times using Currying with Ramda but it mostly makes confusing code so I never did. That was probably 7-8yrs ago and I still use the library daily.
Idk what type of code bases you guys have worked on but I've never had anything close to a situation where it complicated my Codebase or had other devs who were confused by it.
I can imagine someone abusing currying and getting fancy with 10 levels of functions wrapped in another function that gets passed around but honestly that is just bad programming.
Simplicity and clarity is something that is necessary in all programming, FP is not unique, and this library doesn't push you one way or the other, unless you use it for absolutely everything.
> And you absolutely do not need to use currying when using Ramda. It's entirely optional (just pass 2 arguments...)
I think this would be my only argument against Ramda vs something like Lodash, at least in a project with other contributors. Like, I’ll never use the currying because it doesn’t appeal to me. But if another engineer does, and uses it extensively, then we have a situation of “because it was easily available, someone did something that makes the rest of us have to stop and pick apart during review/debugging/whatever”.
How far can this argument be extended? We can go back to vanilla JS/HTML/jQuery; these are all "easy to understand" things. But then too much spaghetti code builds up and then progress grinds down (much like in the Ramda team situation). You need some abstraction and order to maintain consistent productivity levels over the long-term.
I might also argue that sometimes writing the same dumb basic declarative code 20 different times is still faster and easier to read, debug, and maintain than trying to write one master class to handle every possible edge case.
IMO it's one thing if you're writing an engine or library where you have to ensure extremely high quality and good coverage. Or maybe you just work for an incredibly well run outfit where newcomers are deeply trained in the ins and outs of your codebase and style. But no place I've personally worked with has been like that.
If you're just building a bog standard business app for a small or medium business, I don't think purity of code is typically the primary bottleneck. Likely turnover and poor documentation and lack of on-boarding and sudden frequent pivots will be bigger deals, all of which are significantly complicated by over-abstracted code that can't be modularly changed by a single developer.
People worked with vanilla HTML and JS for decades, maybe using server side includes for some basic DRY. It wasn't pretty, it was hecka tedious, but it also wasn't Inception-level nightmarish like some modern abstractions are. There's an ideal level of abstraction for a given team and codebase, IMO, and it's not always "more abstraction is better" (not that you were arguing that). It's just a process of discovery and design by fire that gets you there, not usually a lone genius that produces a masterpiece in any one particular style.
I used Ramda for 3+ years. I like somethings but overall wouldn't pick it up again. FP in general makes it really easy for people to write inefficient passes over data. In addition, debugging or code reviewing ramdajs code can be painful as you often have several layers of call stack with meaningless (curryN, pipe, map, pick) functions. I say meaningless because when your whole app uses ramda, you find it very difficult to know what is being map'd or curry'd.
This is why I prefer a self-optimizing collections library. Many of these inefficient passes can be internally re-wired when combining 'objects' which represent the functions and their rules of composition.
Inline 'debug' pipeline functions are helpful for the second problem you mentioned.
https://rubygems.org/gems/raskell does it in ruby. A similar technique would work in JavaScript, but I don't know of any other libraries that have attempted it yet.
It's a cool library but rarely I get to write something so convoluted in JS/TS that Ramda would make it appear simpler. Rather, it often makes it more convoluted since you can't look directly from the code what a function is doing but have to look up Ramda documentation first. Even that might not help.
Utility libraries like Ramda were much more important 5-10yrs ago before ES6+ modernized JS. I find I have to reach for it less and less. But there's a small few I still use it constantly for (union, intersection, any, all, mergeDeepLeft, times, range).
Ramda putting the options first, data last in the args is a killer move. It means it's easier to bind functions. And many functions can be made variadic, accept all much data as you want to feed, which is a huge boost.
Ramda & fp libraries in general seen to have faded a bit. The utility library everyone knew, lodash, hasn't shipped an update in 2 years. Weird to see this kind of stuff falling away a bit.
I think it's because with FP libraries you generally end up fighting the language to some extent. That's not universally true, I think https://www.effect.website/docs/why-effect does a good job of harmonizing with JS/TS, but it seems to be the default that FP libraries tend to implement stuff that's very category-theory-forward.
I'm very impressed that they managed to express in Typescript type system what looks like algebraic effects, and that the monadic code shown in the docs doesn't look that bad. But TS was not made with that in mind, so I pity whoever has to debug a type error somewhere deep in the code there (probably because some code returns plain JS object instead of computation of it, or the other way around). I think we need to wait for new languages to appear before we can enjoy effective programming with useful compiler errors.
In case you wonder what I mean by "algebraic effects" - your code can make a call to something that may or may not return, is defined based on its environment (was set in some code above in the stack) and may have any side effect. Think "try-catch" but with extra operator to go back to place where exception was thrown and continue from there.
It was never really my jam, but I used to follow the up-and-coming fantasy-land specs with great interest. It just seemed like a sharp dedicated community of folks trying to figure out better fp & algebraic stuff. I'm not sure who trailed off - in general I feel like there's much less connection in tech world, that the tech twitter and every other ultra-active tech channel has somewhat decayed. https://github.com/fantasyland/fantasy-land
Thanks for the links. I know I've seen @gcanti's name a thousand times already, but had utterly forgotten fp-ts.
fp-ts is an interesting library that's totally useless to me because almost nothing has even the tiniest bit of documentation, so you just have to guess at what most of the functions actually do.
Ramda is 10x more accessible to newbie programmers than fp-ts.
fp-ts is something that's theoretically superior and far more thought-out from an FP-perspective. But I wouldn't dare throw that on the lap of a junior programmer. They'd avoid using it anyway.
Not sure why everyone is hating on Ramda, it's extremely easy to switch from Lodash to it and using a data-driven functional approach is very natural in JS/TS. You don't need to use currying at all when using Ramda, that's optional. There's also absolutely no requirement to go full FP to use it, nor does it need to underpin every function.
Utility libraries are exactly that, they are basic helper functions when you don't want to write it yourself. Nor should you have to use ChatGPT every time you need array unions and safe hash merging or slightly more complex map functions or the 10's of other basic stdlib type functions that JS doesn't have.
Lack of documentation frustrated me too. I dont come from a pure FP or Haskell background, so all the type signatures and FP terms don't help at all.
I only used like 2 or 3 categories of things from it since those were thing that i could actually understand. "Either" was the one I used the most
> Disclaimer. Teaching functional programming is out of scope of this project, so the documentation assumes you already know what FP is.
Personally, and some might disagree here, but I think you simply shouldn't use fp-ts before you have a solid experience with "real" FP languages, because any usage of this library is quite messy on its own (after all, JS simply wasn't created for this) and it's easy to create even more mess and get frustrated about it later on. It's also IMO a bit useless to try and teach it because in order to get a good grasp of the abstractions you'd have to practice them yourself and see how they help your real code, and it's something that's much harder to do in JS than in a language which has you covered. You'd rather start with Haskell or PureScript if you wish to learn more.
There's a huge difference between "knowing what functional programming is" and "knowing what the specific functions in this utility project are supposed to do".
Ramda was a hellhole I hope never to use again. We spent a year trying to learn it, finding it unreadable every time we went back to it (originally added by an outside contractor). Eventually we decided to give up, ripped it all out in a week and went back to lodash and never had an issue again.
Ramda code was just impossible to read and made every 5-min job take hours or days... not worth it, no matter the ideology.
I have the complete opposite experience. Ramda is so much more consistent than lodash/fp.
I think your issue has more to due with functional programming in general than rambda vs lodash. It’s not for everyone but personally I really enjoy it.
I don't mean lodash fp, I mean plain lodash. We just rewrote everything into noob style that was infinitely easier to read and also made the code much more concise. The ramda stuff was buried like four levels of abstraction deep and we were able to replace them usually with one-liners.
I worked on a team that used it heavily, the lifecycle went like this. I joined the team and found it hard to read and understand, I learned it deeply, it became really easy to understand and made sense and I kind of liked it, the old people on the team who chose it left, I had new hires join my team, I found my self in the position of having to see how it affected their experience onboarding, I realized it was just kind of more trouble than its worth, we removed it later when we had the opportunity.
I considered Ramda at my last company for a hot minute, but ultimately decided it was too opaque to be a reasonable decision. We ended up using lodash for a little while, but in reality used like 2% of lodash, so I ripped it entirely out in favor of our own small utility collection with really sound typescript typing.
I think, in any project, it’s best to repeat yourself more, and patiently watch to see which abstractions actually emerge.
> but in reality used like 2% of lodash, so I ripped it entirely out in favor of our own small utility collection with really sound typescript typing.
Which is lodash already, wonder why your team feels like the needs to make and maintain a new component for that is valid. Furthermore, lodash is somehow optimized for it's purpose, so the custom implementation may be inferior.
I never aim for file size but AFAIK lodash support tree shaking which is good enough.
Instead of using a library like Ramda or lowdash, I would suggest using modern standard Javascript functions. These kinds of libraries tend to go everywhere (like sand on Tatooine) and you end up writing some hybrid of ‘normal’ Javascript and some pure functional language. If you want to go functional, use Haskell or Purescript. Don’t bend Javascript into something it is not.
I find straight forward, dedicated, precisely typed combinators much more readable and practical to use ie. for iterables (context where it makes a lot of sense) [0] example [1], runtime assertions (through refutations, which are much faster than combinators over assertions) [2], parser combinators for smallish grammars [3] etc.
In many cases vanilla/imperative js is more readable and terse, no need to bring functional fanaticism everywhere, just in places where it gives true benefits and in form that can be understood by peers.
Functional code can be beautiful and can also be unreadable/undebugable. Same with imperative code. It's great in js/ts you can pick approach where the problem is expressed more naturally and mix those paradigms at will.
Hard disagree because Ramda is full of well documented standard native FP constructs. if you must use JS then Ramda can help make the code more correct
Please don't use this. I see a lot of engineers, particularly stubborn seniors, who are dabbling in JS want to bring their functional programming gifts from heaven to us lowly JS peasants. In reality it makes the project inscrutable and also makes it might impossible to onboard any JS specialists to the JS project.
Personally I just don't think Ramda fits really well with JavaScript's mutable and often object-oriented nature. It goes against the grain too much for my taste, and it doesn't work very well with Typescript.
In a professional setting I will probably always reach for Lodash due to it's maturity and mindshare. Personally, though, I really prefer Remeda (https://github.com/remeda/remeda) as a pragmatic and flexible API.
59 comments
[ 2.4 ms ] story [ 117 ms ] threadArguably, depending on what you're building, the pain of integrating an entirely different language into your stack is greater than the pain of simply using something like Ramda (or, better yet, just writing simple, imperative code). YMMV of course—if the problems you're solving lend themselves naturally to purely functional code, maybe something like Rescript or PureScript may be worth it, but for most projects, I'd guess probably not.
1. https://twitter.com/KrComet/status/1653351666446655488
Idiomatic describes the common practices of the ecosystem, without the features (specifically currying, since it's the only one mentioned). How does a codebase that doesn't use these features become unidiomatic?
For instance, the audience that sees fp-ts as a necessary, useful library to code in a manner they find most appropriate will find JavaScript/TypeScript ergonomics verbose, hard to read, and require someone with functional programming experience (see the verbosity according to their docs of TypeScript+fp-ts vs. PureScript: https://gcanti.github.io/fp-ts/guides/purescript.html). That audience would be better suited doing compiled option such as the aforementioned PureScript, or js_of_ocaml, ReScript, derw, Idris, GHC.js/whatever the new GHC compiler JS output is, or similar option. These languages will have the idioms of fp-ts without the heavy reliance on singularly library most of the TypeScript community doesn’t understand that bloats their code bases without requiring the safety (someone can just call .value on a Maybe/Option breaking the safety). (This is not a knock against fp-ts which is a marvel in & of itself, but the audience doesn’t line up with the idioms of the average TypeScript project).
When the company finally came around to using TypeScript, devs could once again reason about code and data and relationships between functions without resorting to abstraction, and the FP stuff died a quick death.
Don't write code that is so far removed from the actual business data that it becomes hard to reason about cause and effect.
Abstraction is something else entirely and is usually what you want. Think more like, "things that have provable laws." Eg: concatenating two lists results in a list that is at least as long as the longest input list, for all lists. Concatenation is the operation on lists that is the abstraction... once you have defined it you no longer have to think in terms of loops and building intermediate lists.
Both were codebases were TypeScript. I think I was more effective in the second, FP, codebase than the first. But really it's more about finding the right balance. One of the flaws in either approach is you try to fit a paradigm forcefully: trying to write Java in JS/TS or trying to write Haskell in JS/TS. The TS code will be better if you understand and accept the JS/TS paradigm and apply patterns where they fit elegantly.
I don't think it makes sense to be "oop" or "fp".
Different problems have characteristics that lend themselves to different techniques.
Difficult code based come from purists who value theory over practicality.
I have a very strongly enforced "don't be clever" rule for teams I manage.
Code should read like a story, good code reduces congnitive load.
I've seen some chains of map, reduce, filter, etc... written by FP purists that are difficult to decipher.
Good code is a love letter to the next developer.
You can do immutable transformations without getting bogged down in FP abstractions.
https://redux-toolkit.js.org/usage/immer-reducers
I've been using Ramda for years and I'm really struggling to see how any of the code in our projects have been complicated by it vs lodash or any other similar utility library.
Idk what type of code bases you guys have worked on but I've never had anything close to a situation where it complicated my Codebase or had other devs who were confused by it.
I can imagine someone abusing currying and getting fancy with 10 levels of functions wrapped in another function that gets passed around but honestly that is just bad programming.
Simplicity and clarity is something that is necessary in all programming, FP is not unique, and this library doesn't push you one way or the other, unless you use it for absolutely everything.
I think this would be my only argument against Ramda vs something like Lodash, at least in a project with other contributors. Like, I’ll never use the currying because it doesn’t appeal to me. But if another engineer does, and uses it extensively, then we have a situation of “because it was easily available, someone did something that makes the rest of us have to stop and pick apart during review/debugging/whatever”.
IMO it's one thing if you're writing an engine or library where you have to ensure extremely high quality and good coverage. Or maybe you just work for an incredibly well run outfit where newcomers are deeply trained in the ins and outs of your codebase and style. But no place I've personally worked with has been like that.
If you're just building a bog standard business app for a small or medium business, I don't think purity of code is typically the primary bottleneck. Likely turnover and poor documentation and lack of on-boarding and sudden frequent pivots will be bigger deals, all of which are significantly complicated by over-abstracted code that can't be modularly changed by a single developer.
People worked with vanilla HTML and JS for decades, maybe using server side includes for some basic DRY. It wasn't pretty, it was hecka tedious, but it also wasn't Inception-level nightmarish like some modern abstractions are. There's an ideal level of abstraction for a given team and codebase, IMO, and it's not always "more abstraction is better" (not that you were arguing that). It's just a process of discovery and design by fire that gets you there, not usually a lone genius that produces a masterpiece in any one particular style.
You replace most core JS constructs with higher order functions.
From there stems your observation.
Inline 'debug' pipeline functions are helpful for the second problem you mentioned.
I had fun with Ramda but I experienced some perf hits when doing big pipe/map/reduces on large data structures
Ramda & fp libraries in general seen to have faded a bit. The utility library everyone knew, lodash, hasn't shipped an update in 2 years. Weird to see this kind of stuff falling away a bit.
In case you wonder what I mean by "algebraic effects" - your code can make a call to something that may or may not return, is defined based on its environment (was set in some code above in the stack) and may have any side effect. Think "try-catch" but with extra operator to go back to place where exception was thrown and continue from there.
- https://github.com/sanctuary-js
- https://github.com/gcanti
I think there's new libraries now rather than the older Ramda and Lodash. The JS ecosystem just seems to move onto newer variants of things over time.
Thanks for the links. I know I've seen @gcanti's name a thousand times already, but had utterly forgotten fp-ts.
fp-ts is something that's theoretically superior and far more thought-out from an FP-perspective. But I wouldn't dare throw that on the lap of a junior programmer. They'd avoid using it anyway.
Not sure why everyone is hating on Ramda, it's extremely easy to switch from Lodash to it and using a data-driven functional approach is very natural in JS/TS. You don't need to use currying at all when using Ramda, that's optional. There's also absolutely no requirement to go full FP to use it, nor does it need to underpin every function.
Utility libraries are exactly that, they are basic helper functions when you don't want to write it yourself. Nor should you have to use ChatGPT every time you need array unions and safe hash merging or slightly more complex map functions or the 10's of other basic stdlib type functions that JS doesn't have.
I wish the docs had more examples or explanations
> Disclaimer. Teaching functional programming is out of scope of this project, so the documentation assumes you already know what FP is.
Personally, and some might disagree here, but I think you simply shouldn't use fp-ts before you have a solid experience with "real" FP languages, because any usage of this library is quite messy on its own (after all, JS simply wasn't created for this) and it's easy to create even more mess and get frustrated about it later on. It's also IMO a bit useless to try and teach it because in order to get a good grasp of the abstractions you'd have to practice them yourself and see how they help your real code, and it's something that's much harder to do in JS than in a language which has you covered. You'd rather start with Haskell or PureScript if you wish to learn more.
Ramda code was just impossible to read and made every 5-min job take hours or days... not worth it, no matter the ideology.
I think your issue has more to due with functional programming in general than rambda vs lodash. It’s not for everyone but personally I really enjoy it.
I think, in any project, it’s best to repeat yourself more, and patiently watch to see which abstractions actually emerge.
Which is lodash already, wonder why your team feels like the needs to make and maintain a new component for that is valid. Furthermore, lodash is somehow optimized for it's purpose, so the custom implementation may be inferior.
I never aim for file size but AFAIK lodash support tree shaking which is good enough.
In many cases vanilla/imperative js is more readable and terse, no need to bring functional fanaticism everywhere, just in places where it gives true benefits and in form that can be understood by peers.
Functional code can be beautiful and can also be unreadable/undebugable. Same with imperative code. It's great in js/ts you can pick approach where the problem is expressed more naturally and mix those paradigms at will.
[0] https://github.com/preludejs/generator
[1] https://observablehq.com/@mirek/project-euler
[2] https://github.com/preludejs/refute
[3] https://github.com/preludejs/parser
stick to stdlib and native lang constructs.
In a professional setting I will probably always reach for Lodash due to it's maturity and mindshare. Personally, though, I really prefer Remeda (https://github.com/remeda/remeda) as a pragmatic and flexible API.