|
|
|
|
|
by jimsimmons
1722 days ago
|
|
I’m constantly baffled by what functional programmers call “simple”. I’m sure this language does a lot with little but calling it simple is audacious to say the least. Why do so many things happen in a single line? Lack of imperative constructs in these languages forces one to nest a lot to be expressive and we get a symbol salad. It’s almost as if the code got passed through a minifier/function inliner. Unless FP embraces more modular and structured ways of programming it’ll always remain a niche. OCaml’s ‘let’ and Haskell’s ‘where’ are steps in the right direction but we need to go a lot farther. To think that any human, with any amount of training, can parse such code in one pass is a fantasy. Since parsing is part of the coding loop, developer productivity is compromised massively. Mathematics has a similar problem where single letter variable names with tiny letters on all 4 corners are ubiquitously used. There’s definitely a tendency in the community to play “symbol” golf. It’s high time we improve ergonomics of both math and FP because the rewards can be tremendous. Rust manages to do some of this to great success and programmers have embraced it so well. |
|
>To think that any human, with any amount of training, can parse such code in one pass is a fantasy.
This is exactly what I think about OOP object hierarchies, where one function is distributed over 15 files and you need to keep the state of the object in your mind when thinking about what this function could do.
In contrast when reading F# I often only need a single-pass, because with let bindings and the |> operator I can see how the data goes in, what is done to it one step at a time, and what comes out. It's as simple as it gets. Granted that single pass might be slow, but that's because it is dense and doesn't contain so much extraneous noise, like the boilerplate standard-OOP creates.
If you then work with a team that uses proper railway oriented programming. I.e. not only pure functions but that use the type system to express failure. I.e. a sqrt function that returns Some(sqrt(x)) for x >= 0 and None for x < 0. Then it becomes even more simple and explicit to understand what can go wrong and how that is dealt with. Instead of some sub-sub-sub-function throwing an exception that you never heard about until it happens at runtime.
You can almost never single-pass OOP code, especially if some architecture astronaut that memorized the GoF book had their way with it.