Hacker News new | ask | show | jobs
by gr4vityWall 647 days ago
The article itself was well written, although I'd appreciate if the author was more "to the point" with the examples.

FP never resonated with me and never fit the mental model I have for programming. I tried learning Prolog and Haskell a few times, and I never felt like I could reason about the code. This line from the article:

"[..] With functional programming, whether in a typed language or not, it tends to be much more clear when you've made a trivial, dumb error [..]"

wasn't my experience at all. In my experience, what made it clear when I made a trivial/dumb error was either having good typing present, or clear error messages.

I do always try to use the aspects from it that I find useful and apply them when writing code. Using immutable data and avoiding side effects when possible being the big ones.

I'm glad FP works for the blog author - I've met a few people that say how FP makes it easier for them to reason about code, which is great.

4 comments

I’ve wondered for some time how this breaks down along preferences for math vs (human) language, or for proofs/formula thinking vs algorithmic.

I find FP concepts easy enough to grasp (provided they’re not demonstrated in e.g. Haskell) and even adjacent stuff like typeclasses or monads or what have you aren't a stumbling block, and I'm plenty comfortable with stuff like recursion.

… but I'm firmly on the language-is-more-natural-for-me side of things, and find non-algorithmically-oriented math writing incredibly difficult to follow—I have to translate it to something more algorithmic and step-oriented to make any headway, in fact. I find languages like Haskell nearly illegible, and tend to hate reading code written by dedicated FP fans in just about any language.

the "FP is for math people" meme IMO is incorrect and comes from FP mainly being used to refer to Pure FP, aka Haskell.

While Monads and co. are interesting constructs, I think the main thing with FP (pure or not) is immutability by default.

That alone makes code so much easier to think about, in my experience.

One can do FP in languages not commonly associated with FP by just not (re)assigning variables. FP languages just make it increasingly hard to do so.

To it wasn't really math, even though there was some of that, it was about the amount of concepts and devices that have to work together.

Ability to reason about expressions means everything can be run and have a result without side effects (unless you allow, say, lisp effectful builtins). The fact that everything is built around function means you can always unplug or compose them. All this with a very reduced set of ideas and syntax (at least for the core)

On the other hand most imperative languages required you to pay attention to state, which is rapidly a mental dead end, with a lot more ceremony and syntax. At least before the 2010s .. nowadays everybody has expression oriented traits and lambdas.

After learning ml/haskell and doing interesting things with a kind of clarity.. I tried going back to c/python and suddenly a lot of errors, issues and roadblocks started to appear.

Then you have the parallel case.

When I was younger, I found writing essays, mathematical proofs, and imperative code very similar activities. Functional programming was difficult, because I could not find the right way to think about it.

Over time, I have slowly become better at functional programming. But I'm now worse with proofs and essays due to the lack of practice.

I think I find a lot of FP code harder to both write and read (even code I've written myself), but when the FP code is written and compiles, it is much more likely to be correct.

It's an annoying trade off, to be sure. With a language that makes writing FP code ergonomic and idiomatic, though, I'm usually going to choose to write that way.

But even in languages where writing in an FP style is a chore (if it's even possible at all), you can take some lessons. The C I write today is more "functional" than 25 years ago, when I'd never even heard of functional programming. I try to avoid global state and mutation and side effects, and write more pure functions when I can. I think about my programs as transformations of data, not as a series of instructions, when I can. My C code today is not very FP at all when you'd compare it to something written in Haskell or Scala or whatever, but it's, IMO, "better" code than what I used to write.

In 2022 I went back to a C-based open source project that I used to work on heavily in the mid-'00s. Reading a lot of that old code makes me cringe sometimes, because the details at the micro level make it really hard to reason about behavior at the macro level. As I've been working on it and fixing things and adding features again, I'm slowly morphing it into something easier to reason about, and it turns out that using functional concepts and idioms -- where possible in C without having to go into macro hell -- is essentially what I'm doing.

>I think I find a lot of FP code harder to both write and read (even code I've written myself), but when the FP code is written and compiles, it is much more likely to be correct.

You're probably thinking about haskell. It's like this because of the type checking combined with the FP makes haskell especially robust.

That being said FP is programming nivana. The FP function is the most modular unit of computation in CS. By writing an FP program composed of functions you have broken down all sections of your program into the smallest form by definition.

>it is much more likely to be correct

It is not. FP programs measurably have at least as many defects as non-FP programs.

There is literally no reason to subject yourself and, worse, your users, to the garbage of FP.

I even know where this comes from. Some very popular circa 2000 book on programming that coined this dogma without a proof, based on the fact that writing a type-correct program in haskell isn’t trivial.
FP doesn't have to be a religion; in Common Lisp it's just an idea, an ideal to aim for perhaps.
Same can be said about Clojure. Although Clojure usually described as an FP-language, it's not "purely FP". In general, I find that Lispers typically don't concern themselves with the popularity of certain tools or techniques; They don't care for things like MSFT marketing shoveled in your mouth. Die-hard pragmatists, they'd use an instrument if it makes sense. They don't give two shits about OOP propaganda or extreme functional purity, or the notion such as "not using static types is immoral" and shit like that, they'd use object-orientation where it makes sense; metaprogramming, where desired; constrains, if applicable; concurrency when required, etc. All that without any zealotry - just pragmatic use of the right tools for the job, embracing Clojure's flexibility and power while staying true to the only core "philosophy" - "simple made easy".
As a dyed-in-the-wool Clojurist, I appreciate leveraging the functional aspects as much as immutable by default data structures. It takes a while to stand back and realize, but once you have a large program that is mostly all passing immutable hashmaps all around to static functions, testing becomes soo much easier and reasoning about how all the code is glued together becomes far easier. You know with certainty that code doesn't have all the ordering problems that come with Objects, whether you called some random function on some object at aome time that cached some instance variable that should have been shared with some other object. Reasoning about that is nuts, and the status quo for most OO code I've seen. If you know that most code only is implemented with pure functions with immutable data, then the ordering questions are nearly completely gone. You can now refactor so much easier as well without risk of subtle ordering related breakage. And then Clojure has atoms and channels which are very nice, thread-safe constructs that also are far easier to know your code won't have memory safety issues. I have dabbled at learning Rust or Haskell or Swift but Clojure gets so much so right.
> You know with certainty that code doesn't have all the ...

You have this certainty to the extent that the compiler enforces it.

If you like language X because it's 'pragmatic' instead of 'religious', then you make the pragmatic choice of not knowing with certainty.

I know, right? It may feel weird at first - with immutability, parentheses and prefix notation, but once you grok REPL-driven interactivity and structural editing - so much about programming becomes intuitively simpler.
I have the same experience. I learned Haskell because of the aura of prestige around it, and must admit I still love the syntax, but moved over to Clojure as my daily-driver as the practicality of "just use maps" is incredible.

IMO the article misses the point there. Immutability is the "thing" in FP.

Yes! I spent months (maybe even years) trying to understand Haskell, and for the love of god, I just couldn't wrap my head around so many things. I just wanted to build stuff with it, but it felt like becoming a doctor - getting a bachelor's, passing MCAT, then four years in medical school, then residency program, then getting a license and a board certification. All that for the sake of knowing how to properly apply a band-aid (or something). Except, with Haskell, there's no rigid structure, there's no single, proven, 100% guaranteed path to glory - it's all blur. There's no "10 steps to build a website with Haskell". And then I picked up Clojure, and OMG! everything suddenly started making so... much... sense... All the things in Haskell, for which I had either no clue or had some shallow understanding - that all became so much more intuitive.
It definitely took me at least 3 years of reading and practicing to be able to get to -at least think- understand it, and I still think I would be considered pretty incompetent by people that do Haskell daily.

There is no doubt in my mind that the Purity concept in Haskell is taken to an impractical degree. I happen to like it, but it does not make developing software with it any less of a troubled experience. The defining concept in comparing Haskell and Clojure for me is the pareto principle :D Clojure gets 80+% of the way there, without 80% of the self flagellation.

There is also a consideration of static typing vs dynamic but that's a whole other can of worms.

Simply, in Haskell all mutability is delegated to the runtime while in clojure one has the freedom and responsibility to manage it directly, and boy does it gain in simplicity for it!

I know I am preaching to the choir but it's pleasant to find a similar experience and share thoughts!

It's possible that what makes functional languages easier to reason about and debug is the fact that they allow the types to capture a lot more information than the imperative languages.

What would also explain why Prolog has none of those benefits. If you don't use the extra information, it can't do any good.

But if it is really just that, it can be replicated over imperative languages. Anyway, Rust is evidence that there is something to that idea.

Not disagreeing with you, though I'd say Rust syntax also feels very hard to understand most of the time. I think function signatures is something that I distinctly remember getting very complicated sometimes.

Which is unfortunate, as I like the principles behind it. I wonder if someone will ever write a Rust-like language that has a syntax closer to Java or Haxe.

Oh, Rust type signatures are harder to use than even Haskell.

But that's because there are a lot of low level detail that must go into them. Most of the complexity that C developers tend to ignore (and create wrong programs) goes there, explicitly. If you don't want the detail, you can make them simpler.

That said, I have a long rant about how Haskell-like types ignore the entire "algebra" thing from algebraic types, and could be way more expressive and simpler to use.