this post was submitted on 03 Nov 2022
0 points (NaN% liked)
General Programming Discussion
7803 readers
1 users here now
A general programming discussion community.
Rules:
- Be civil.
- Please start discussions that spark conversation
Other communities
Systems
Functional Programming
Also related
founded 6 years ago
MODERATORS
you are viewing a single comment's thread
view the rest of the comments
view the rest of the comments
I've been working with Clojure and most code is written using pure functional style. As a concrete example, Pedestal HTTP server has around 18,000 lines of code, and 96% of it is pure functions. All the IO and side effects are encapsulated in the remaining 4% of the code.
This has been a common scenario for the vast majority of Clojure programs I've worked on in the past decade. Core of the application does data transformations using native Clojure data structures. This is where all your interesting business logic lives. The interop lives at the edges and typically used for stuff like database drivers, HTTP servers, file access, and so on.
And I completely agree regarding immutability being the sane default.
I find that there are two major problems with OO. First is that it takes a lot of effort to learn how an API works because you have to pass object graphs around, and each object is a unique snowflake in terms of methods and behaviors it has. So, you often have to learn how dozens or even hundreds of objects are intended to be used.
Second, is that each object is effectively a state machine, and your application is structured as a graph of interdependent stateful objects. This makes it effectively impossible to reason about any non trivial application because all these relationships are too big to keep in your head. The only thing you can do is fire up a debugger get the application in a particular state and inspect it. Unfortunately, this is just heuristic since it's hard to guarantee that you've covered all the paths leading to a particular state.
Meanwhile with FP library APIs become data focused. You call a function, pass it some data, get some data back, and that's all you need to know. Data is both transparent and inert in nature. You don't have any behaviors associated with it, and you don't have to know what methods to call. The scope of information you need to keep in your head is drastically lower because you're able to do local reasoning.
As an FP-fan interested in Clojure, how does one track if functions are pure in Clojure? I had assumed this was not possible, due to it not being statically typed (although I gather there is 3rd-party support for gradual typing).
There is nothing stopping you from making an impure function, but the language naturally guides you towards writing code that's composed of a bunch of functions that transform data. Clojure also has explicit containers for mutable data and those have their own semantics, so you can't accidentally cause mutation.
It's also worth distinguishing between different kinds of side effects. One type of effects is doing things like logging, which in my experience are generally harmless. The type of side effects that I tend to worry about are the ones that couple functions together via mutable references.
And yeah, Typed Clojure library lets you do gradual typing, but it hasn't really caught on with the community so far. Most people prefer using runtime contracts with Spec and Malli.
Thanks - I was just wondering how this somewhat precise statistic was obtained.
Otherwise, all that makes sense generally, though I tend to model logging as an effect in statically typed languages with effect systems. But I agree that it isn't the most important thing!
I'm not sure how the statistic for Pedestal was obtained, don't recall if the talk mentions it or not. There are static analysis tools for Clojure like clj-kondo that can provide these kinds of insights. You could see what parts of the code are pure based on what functions get called for example.