How to deal with dirty side effects in your pure functional JavaScript
jrsinclair.com
jrsinclair.com
We shouldn't have to rely on loopholes to satisfy any hard and fast rules. A lot of writing good code is just being reasonable and explicit.
If you call a function to launch a missile, you'd expect that it's just going to launch a missile and do nothing else. So why are we so concerned that it contains side effects? What other option do we have?
Logging is a bit different. For many, logging is probably a necessity to allowing you to look back and and understand context when things go wrong. But when you're programming functionally, I find that it's much easier to get a grasp on things compared to procedural code, which can become spaghetti much quicker. I also find that bugs are much less common, or otherwise much more obvious in functional code, negating the need for logging in the first place (for functional code).
E.g., suppose you have an SPA that pulls data from a number of Rest endpoints, then does some transforms on that data, produces some HTML and eventually updates the page DOM accordingly.
It makes sense model the "transform" and "produce HTML" steps as pure functions, because they are effectively side-effect free calculations. I think it might also make sense to cheat and treat the Rest calls as pure functions, because this will let you find out quickly in which order you should do them and which of them you can run in parallel.
But why would you want to model the eventual update step itself as a pure function?
I agree with you, functional programming and pure functions have a lot to offer but they have a place and as soon as you start pushing them into every corner of an app you end up just creating problems for the future.
Honestly, I'm getting downvotes for this but probably from the same types who thought CQRS or micros-services should be taken to the extreme and ended up totally crashing and burning.
Why can't techies see an approach, appreciate where it can help, and apply it where its useful? Why does it seemingly have to always be an all-in-or-nothing proposition?
This is one of the reasons I love JavaScript so much, and one of the things that stood out to me in the article. JavaScript is extremely expressive and allows you to write code without worrying about following some specific paradigm, which always seemed to me to be one of its biggest strengths, not a weakness as the article seems to suggest.
This post offers an interesting discussion on this subject: https://www.destroyallsoftware.com/screencasts/catalog/funct...
So you can more easily debug the application across incorrect state changes: if your update function is pure, you can very easily compare the state-before and the state-after.
This maintenance of observability is a superset of the unit-testing bit legulere talks about, and also opens up new features e.g. undo/redo becomes trivial, just re-set the state to whatever you need. If you modify the application state in-place, things become much more complex.
In a way git can be treated as that, the HEAD commit is the application state, when you "modify the state" you don't modify that commit in-place instead you create a new commit, then move HEAD to that new commit. Only the second step is impure. Even amending a commit is the latter, you're not actually modifying the old HEAD, you're creating a new one from it then moving HEAD from one to the other.
I've been at 3 companies now where "time travel" has never been used and web app development has been more painful than it needed to be.
It feels very much like the proper (from a CS perspective) approach, but the language and libraries just aren't there to make it feel productive or fun. Is there any meaningful work being done to address this, or is it perhaps a boiling frog scenario?
I agree that, currently, languages and frameworks don’t make things like time travel easy, insofar as such a feature is not often built in. However, technologies like Flux and Vuex are paving the way for such things to become mainstream. It’s worth pointing out, too, that both were inspired by Elm’s runtime architecture.
Functional programming is, only in the last half-decade or so started to see a relatively dramatic rise in popularity. I think this trend will continue for a long time to come. For that reason, it’s worth learning more about these concepts.
That's how Elm works and it's glorious. IIRC there are also clojurescript systems working that way (application state is an atom and updating it is a transactional CAS).
But of course it also helps that the language itself lends itself to this, so you want a language which is expressions oriented and there persistent data structures are the default (or even the only) option. You can do the same thing in e.g. Javascript, but the language doesn't really help and it's easy to slip.
I think the benefit of js functional programming is composability; you can build complicated functions from smaller, more focused functions. This ends up being easier to read and debug if you run into some unforeseen logic error.
It really depends on what you are building, but it's hard to avoid logic errors when working with the DOM explicitly. It's a lot easier to map over an empty array than to manipulate DOM elements that have been removed from the screen.
const fOne = fIncrement(zero);
const fTwo = fIncrement(one);
const fThree = fIncrement(two);
// And so on…
Seems like this implies that each number relies on the previous. A bit confusing?Unless you code very defensively, there's no way you can guarantee purity, especially in async programming.
Given f(x) = y, if x is a regular JS object, you can't guarantee purity. It works many times because of the single-threaded nature of most JS engines, but the moment you do something async, you can't guarantee x hasn't changed.
That's one of the many reasons I like ClojureScript. It offers interesting guarantees that let me write robust code.
Instead of reading from opaque file handles use three-tuple of (hash, offset, length). Now you have pure input.
Instead of printing to a stream, modify a git repo. Now you have pure output.
Aren't there already a few? OCaml?
What's "serious" in this context?
I read somewhere that an OCaml to LLVM compiler was attempted, but people had problems with the GC aspect. I don’t know if this claim is true, though.
I was thinking of the same, but I hadn't/haven't used it so you may be right it never came to fruition.
It compiles to wasm object files and links files with LLVM's LLD. My current challenge is ensuring that the stdlib correctly translates to wasm. The C parts are not translated yet - which means: no gc, no exceptions, no tail calls.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Meanwhile infix operators are just syntax sugar and can be emulated in other ways.
Aren't they both FP languages?
(I’m assuming you meant to convey a slightly negative connotation through “mathematical”. Though I myself do like PS too)
[0] https://www.tweag.io/posts/2018-05-29-hello-asterius.html [1] https://github.com/tweag/asterius