Ask HN: How has functional programming influenced your thinking?
Functional programming is a programming paradigm. Has learning this particular paradigm caused any paradigm shift to your thinking about programming or problem solving in general?
If so, how?
50 comments
[ 2.7 ms ] story [ 117 ms ] threadb) To me, functional programming was tremendeously useful when learning to program. Not sure exactly why, but it clicked with the way my brain works.
Did you use any specific book for the purpose?
Two classics:
The Little Schemer, and the first few chapters of Structure and Interpretation of Computer Programs.
The premise is this: you have two arms and two hands. They can be used in a controlled manner to do lots of useful things. They can also be swung around in a wild fashion that results in punching yourself in the face.
The concepts functional programming rallies against, like mutable state, are like arms: useful in certain circumstances, dangerous in others. But that's the thing: You don't have to write code in a dangerous way. You don't have to punch yourself in the face. That is to say, functional programming has reinforced my belief in the imperative style of programming.
FP also taught me a lot about types, which allow me to define even better the parts that construct big programs.
Functional programming (similarly type systems) is another great technique to use when you have constraints that you’d like to be automatically enforced by the compiler (referential transparency, equational reasoning, etc.)
When I have to use a language that doesn’t allow expressing problems functionally when that would be the ideal expression of it, I feel I’m using the wrong tool for the job.
The word "wisely" does the heavy lifting here. Put it this way: would you rather work on a legacy code base that has goto statements all over the place, or on one where strict standards were applied?
I find this is the relevant question when it comes to programming styles.
Banning “goto” is one of such, but also allowing to be utilized under certain conditions to write state machines for example.
I think we will miss innovations if we are dogmatic about features.
“Learn the rules like a pro, so you can break them like an artist.” - Pablo Picasso
Composition is hard
In Haskell or other typed fp languages, you have all these well-recognized and well-used typeclasses and their attached methods, conceptually equivalent to generic interfaces in c#/java. It's easier to adapt new data structures into these interfaces and have all your existing functions immediately work with them.
The "mathematics" part here is that these typeclasses are backed by category theory that prescribes their behaviors.
There are plenty of random readings you can find, so I won't recommend them. Someone's bound to argue with me on what's a better entrypoint.
I do want to note that you don't need to use haskell or an fp language to use these concepts. Example: a complete set of fp data structures in typescript https://gcanti.github.io/fp-ts/modules/. It's just that fp languages make using them much easier because of their type system.
> The list, Lisp's native data structure, is largely responsible for such growth of utility. The simple structure and natural applicability of lists are reflected in functions that are amazingly nonidiosyncratic. In Pascal the plethora of declarable data structures induces a specialization within functions that inhibits and penalizes casual cooperation. It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures. As a result the pyramid must stand unchanged for a millennium; the organism must evolve or perish.
I love programming with typeclasses based on mathematical abstractions. I guess it's because they actually have a chance of standing unchanged for a millennium.
https://doisinkidney.com/posts/2017-10-13-convolutions-and-s...
It’s mostly things at this level, and not larger structural things. I don’t understand (and probably never will) the whole dance around using monads for side effects, aka the entire reason we run software in the first place. Your problem is “I want to update the database” and someone starts talking about stuff like “bind” and “shift”. Now you have two problems.
Having values and having your program phrased as transformations of values makes for straight forward, easily tested code. Provide inputs, assert outputs.
What was my frame for reference was piping input and output in bash programming and various declarative DSL like SQL or the Hashicorp language, functional programming in Clojure seemed quite productive and grokable, so I dove into that.
Nowadays I think about computational requirements in terms of relations among behavioral dependencies. Like, "I want to perform operation O on input A and return a B. To do this, I'll need a way to a -> b and a way to b -> b -> b." I often pass these behavioral dependencies in as arguments and it tends to make the inner core of my programs pretty abstract and built up as layers of specificity.
Zooming out nearly all the way, it makes me feel tethered in a qualitatively unique way to certain deep truths of the universe. In a Platonic sense, invoking certain ideas like a monad make me feel like I'm approaching the divine or at least one instantiation of a timeless universal that operates outside of material existence.
I'd imagine some mathematicians might see the universe in a similar way - one where immortal relations between ontological forms exist beyond time and space and at the same time can be threaded through the material world by intellectual observation and when those two meet a beautiful collision occurs.
1. https://typelevel.org/cats/
2. https://github.com/precog/matryoshka
For tree based processing functional programming Ocaml and Haskell are meant to be very effective. Especially if you can think of your problem as a functional isomorphism.
When I created my toy database I used a pipeline of tuples that were reduced. Relational data is easy to process with functional ideas.
Clojure pipelines are similar.
My problem with functional programming is its readability. If someone had a different idea of how they would approach the problem functionally, it can be tricky to read the code that performs the functional mapping.
Smart doesn't work that way. Shame on me for not learning the same lesson from OOP. These are good technologies, but toxic culture undoes all the good and more.
End result: We can't have nice things, because the arguments are bogus.
I imagine there are normal shops and devs using Elixir, F#, certainly Elm.
It is much more enjoyable working with immutable data structures which tends to remove long distance dependencies from your code. Instead, decisions are made locally and results passed up vs directly changing state in an object graph.
Not all problems are easily turned into a pipeline of pure functions, but it is worthwhile to learn which category of problems can.
Lean a Lisp. It will up your game.
You just know deep down whenever you touch JS or some other language that there exists a better way of programming.
Through that I understood that the state of a program what causes most of the confusion and without variables reasoning about the code becomes much simpler. Without state everything becomes nice.
Through that I understood that state is the enemy, and global state is the greatest enemy of understanding. Later I realized that this same principle strongly aligns with testable code as more state is what makes code less testable.
It also forced me to use a lot of recursion which is nice as I understood it much better than in that single "Fibonacci number" example.
I also learned to write code that can safely run in parallel as functions without side effects and state are the most ideal candidates for that.
Back in college I took a programming class that used Haskell and not having any other do way to perform iterations other than with recursion, you're forced to understand recursion. With imperative languages, you can always fall back to ol' for and while loops so you get the impression that recursion is simply a waste of time.
What functional languages don't have is the ability of a function to modify state in a way that persists beyond its lifetime, which makes the temporal axis of a program more or less the same as the call tree.
State is the enemy only insofar as you need more than the call tree to figure out its value.
But that state shouldn't persist beyond the request, otherwise you have problems. You shouldn't have one request modifying the state of other running requests, etc..
In this context what I mean by stateless is a function call that only depends on the input - given the same starting position it will always produce the same result.
In real world what messes up this behaviour are fields in a class, properties in a file, persistent data of any form and globally initialized variables unknown to the caller.
You don't and will never have an easy way to reproduce, find out or modify these values so ideally you should not put data in database, not use property files and not use fields in classes :)
Yeah... I never said it was easy or doable in real world. But it is a great compass to drive towards a good architecture.
A quick and dirty pipeither() with a "poor mans" either monad and you get a lot of the benefits of pure, testable functions, easy maintenance and debugging etc.
It's not Haskell, but its good.
[1]
What's an actual concrete use case of this? Where specifically would I use immutable variables to make my code better?
A helpful consequence of immutability is that it makes concurrent and parallel programming easier. Take Erlang/Elixir. The main reason it’s able to share state between processes so easily is because it completely copies values from one process into another’s mailbox. Compiler guarantees you don’t pass a reference. If it were the same memory, two processes could mutate and unwittingly break one another.
Another helpful aspect are the patterns of code you end up writing. It’s common in Node and C#, even Go, to pass a mutable context or stateful object with a bunch of hands reaching in to read and write. Because you can naturally do this, you naturally also tend to program like this. Cue data access issues again.
But if you guarantee immutability at the runtime level, it changes how you write code. You tend to write functions in a “transformer” / pipeline style. there’re great examples of open source codebases to read from that exhibit this. See a typical F#, Haskell, and presumably Scala web api codebase, and see how they handle state.
It is easy to avoid, but if you can't do it anyway, the potential for bugs go away.
A good engineer understands the sweet spot of these tradeoffs. Use functional techniques with immutable data structures in the right places where performance is unlikely to become an issue.