The BIG pain point for me, though, looking at my 10-year-old JS, is that it's all made of "classes" built up by modifying SomeFunction.prototype, which then get wrapped in a self-invoking anonymous function and returned as an object, just so you can call `new SomeFunction()` ... `SomeFunction.actualFunction()` as if SomeFunction were a class. That and also the lack of arrow notation; modifying Function.prototype itself to use .apply() to bind events to scope (or else just an endless abyss of .bind(this) in event callbacks)...
Today I don't even write promises anymore. Using fp-ts and piping data through Task and TaskEither monads feels so much better and easier to write and maintain.
I know what most of those words mean, but I have no idea what they mean in that order. This feels symptomatic of the general state of Javascript, where I've not written any for a year or so and am now wildly out of date.
People often complain about how complex it is, but it’s really just a matter of passing endofunctions through transplainer pipelines so that you can asynchronously hydrate the islands.
Seriously, once you understand the ui as a superposition of entangled complex-valued eigenstates that only collapse into place once an observer visits your page, frontend development becomes so much easier.
Why would you ever even need to pass an endofunction through a transplainer pipeline? There are far more efficient ways to do async island hydration in Elm.
The thing is — I'm actually quite stupid. I don't even understand what a monad is. I just FP because it's a good tool. So, here's an example. I'm building an automatic storyteller on top of GPT-4 — it's a toy project to build the LLM-based functional library. Here's how the code looks, with comments: https://gist.github.com/golergka/3fdc6b9cf9717bd1c0b315925a8...
This is literally 20 lines of code, and it doesn't take that much to learn to read it easily. When I originally implemented LLM-based applications in Python, it would make about 10 times more, and all the components wouldn't be as modular. I can change the functionality of this code in a very drastic way with minimal code changes and with safety of completely statically type checked code.
Thing is: you don't read it easily. It's basically a custom DSL bolted on top of Javascript that hides actual functionality beneath an Everest of abstractions.
That is, it's a yet another library trying to force Haskell/Haskellisms into Javascript and claiming with no proof other than emotions that it's so much better and more readable than something.
It can be further simplified. For example, you don't need two separate functions to extract the first chat completion message etc.
This version:
- uses existing language constructs
- can be immediately understood even by the most junior devs
- is likely to be 1000 times faster
- does not rely on an external dependency that currently has 143 issues and every two weeks releases a new version adding dozens of new methods to things
* all but the first results are not arrays, so the `.map` function doesn't apply
* doesn't handle async operations
* catches ALL exceptions here, whereas my code only handles statically typed and well-defined errors
* doesn't include the GPT-generated message and messages from the first steps in the error
* doesn't include retry functionality
I appreciate this argument and time you've taken to read my code and write this counter-argument. Please don't mistake my direct code for animosity, I clearly see that you're a smart person even though I think you're wrong about this.
Also, the fp-ts doesn't add any runtime performance overhead here. It's a couple of extra function calls for a call that has an async operation that likely takes 30 seconds (GPT-4 is slow). Obviously, if I was writing code that runs thousands of times per second, I would not use FP.
It was also somewhat tongue-in-cheek, and a five-second copy-paste of your code.
> all but the first results are not arrays, so the `.map` function doesn't apply
Unless you return arrays from all functions.
Not that different from wrapping every result into seventeen layers of functional abstractions. What do you think flow or flatMapEither does?
> doesn't include the GPT-generated message and messages from the first steps in the error
Of course it does. That's what the catch is for.
Or you could use an additional map if you want.
> Also, the fp-ts doesn't add any runtime performance overhead here. It's a couple of extra function calls
Of course it adds runtime performance overhead. Any additional function call adds a performance overhead. And in your case it's dozens of functions wrapping trivial functionality.
> Obviously, if I was writing code that runs thousands of times per second, I would not use FP.
Then why use it here? If the alternative is more readable, more traceable, has better stack traces for errors, doesn't require an unstable ever changing dependency...
> Not that different from wrapping every result into seventeen layers of functional abstractions. What do you think flow or flatMapEither does?
It changes what functions return without having to adapt them to particular use-case.
> Of course it does. That's what the catch is for.
To achieve that, the catch has to assume that the functions throw exceptions with particular data. This cannot be enforced with type system, and once again, tied implementation of these functions to this particular context where they're used.
> Of course it adds runtime performance overhead. Any additional function call adds a performance overhead.
The function call overhead is a couple of dozens nanoseconds at best. And we're talking about a remote call that takes 30 seconds to complete and costs $0,05 per OpenAI's API.
The overhead you're talking about is 0,0000001% in time and I think a few orders less in cost.
> If the alternative is more readable
It's not. An alternative that would actually have all the same functionality would be 4 times as long and more importantly, much more complex to change in a meaningful way.
> more traceable, has better stack traces for errors
But that's the point: we don't need stack traces. We don't need exceptions for errors that we know how to handle.
That's like complaining that a GC-based language doesn't have a tool like valgrind to handle error allocation and deallocation issues.
> doesn't require an unstable ever changing dependency...
I've never had a breaking change from fp-ts and don't expect it to ever happen because of the nature of the library.
Really? I feel like Javascript has been more or less the same since we got `class` keyword. People have been using “FP” words like monad or Either in Javascript for more than 10 years. They weren’t mainstream then and they aren’t mainstream now even if you see them written in an HN thread.
The BIG pain point for me, though, looking at my 10-year-old JS, is that it's all made of "classes" built up by modifying SomeFunction.prototype, which then get wrapped in a self-invoking anonymous function and returned as an object, just so you can call `new SomeFunction()` ... `SomeFunction.actualFunction()` as if SomeFunction were a class. That and also the lack of arrow notation; modifying Function.prototype itself to use .apply() to bind events to scope (or else just an endless abyss of .bind(this) in event callbacks)...