What's Functional Programming All About?
Functional programming is often misunderstood, focusing on irrelevant details. It emphasizes complexity control and clearer structures, contrasting with imperative programming's challenges, promoting better error handling and refactoring.
Read original articleFunctional programming (FP) is often misunderstood, with many explanations focusing on irrelevant details or misconceptions. This blog post aims to clarify the core principles of FP, contrasting it with imperative programming through the analogy of a kitchen recipe, specifically Michael Chu's Classic Tiramisu. The author emphasizes that FP is not merely about wrapping imperative code in helper methods or using static types, as these do not capture the essence of FP. Instead, FP encourages a deeper understanding of complexity and control over code, allowing for better refactoring and error handling. The post critiques common misconceptions, such as equating FP with writing in Haskell or using compile-time AST macros, asserting that these do not define FP's core. The author illustrates how an imperative recipe can be difficult to manage and refactor, highlighting the challenges of parallel execution, error recovery, and dependency management. In contrast, FP promotes a clearer structure that can simplify these processes. The discussion aims to provide insights that resonate with both newcomers and experienced practitioners in the field of functional programming.
- Functional programming is often misrepresented by focusing on irrelevant aspects.
- It emphasizes understanding complexity and controlling code rather than just using helper methods.
- Common misconceptions include equating FP with static types or specific programming languages.
- The analogy of a kitchen recipe illustrates the challenges of imperative programming.
- FP offers clearer structures that facilitate better error handling and refactoring.
Related
I Probably Hate Writing Code in Your Favorite Language
The author critiques popular programming languages like Python and Java, favoring Elixir and Haskell for immutability and functional programming benefits. They emphasize personal language preferences for hobby projects, not sparking conflict.
Post-Architecture: Premature Abstraction Is the Root of All Evil
The article explores Post-Architecture in software development, cautioning against premature abstraction. It promotes simplicity, procedural programming, and selective abstraction for maintainable and efficient code.
My programming beliefs as of July 2024
Evan Hahn emphasizes tailored programming approaches, distinguishing "simple" from "easy," promoting testability through modularity, and advocating for ethical coding practices prioritizing societal impact and nuanced thinking in software development.
Functional languages should be so much better at mutation than they are
The article explores integrating mutation into functional programming, discussing methods like restricted mutable structures, local mutation, and linearity, while highlighting challenges and the need for a balanced approach.
Functional programming languages should be better at mutation than they are
The article explores integrating mutation into functional programming, discussing methods like restricted mutation, local mutation, and linearity, while emphasizing the need for user-friendly solutions that maintain functional principles.
- Many commenters emphasize the flexibility of programming paradigms, noting that FP concepts can be applied in various languages, including those traditionally seen as imperative.
- Some express skepticism about the claimed benefits of FP, particularly regarding error handling and clarity, sharing personal experiences that contradict the article's assertions.
- There is a discussion about the complexity of FP, with some arguing that it can increase cognitive load, especially in real-world applications with intricate dependencies.
- Several comments highlight the importance of immutability and side effects in programming, suggesting that these concepts are central to understanding FP.
- The debate also touches on the philosophical aspects of programming, such as the separation of data and methods in FP versus the encapsulation in object-oriented programming.
Anyone who likes this article on my blog should check out this presentation on applying this philosophy to build tools
https://youtu.be/j6uThGxx-18?si=ZF8yOEkd4wxlq84X
While the blog post is very abstract, the video demonstrates how to take the abstract philosophy and use it to help solve a very concrete, very common problem
I'm always surprised reading comments on these topics, people saying they don't grasp FP. But don't we use higher-order functions, closures, combinators all the time in most mainstream languages? How hard can it be to learn OCaml or Clojure for someone who use closures all over the place in JS?
Monads have a steeper learning curve, but besides Haskell, they aren't that pervasive. And there are constructs with similar flavor in mainstream languages too (result types in Rust...)
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.
However, FP's benefits can be overstated, especially for complex real-world systems. These systems frequently have non-unidirectional dependencies that create challenges in FP. For example, when component A depends on B, but B also depends on a previous state of A, their interrelationship must be hoisted to the edges of the program. This approach reduces races and nondeterministic behavior, but it can make local code harder to understand. As a result, FP's emphasis on granular transformations can increase cognitive load, particularly when dealing with these intricate dependencies.
Effective codebases often blend functional and imperative styles. Imperative code can model circular dependencies and stateful interactions more intuitively. Thus, selectively applying FP techniques within an imperative framework may be more practical than wholesale FP adoption, especially for systems with complex interdependencies.
To me, all of this could be summarized by "no side effect".
What's Functional Programming All About? - https://news.ycombinator.com/item?id=15138429 - Aug 2017 (139 comments)
What's Functional Programming All About? - https://news.ycombinator.com/item?id=13487366 - Jan 2017 (2 comments)
Writing functions FP is essentially all about returning results from a function, which is proof that that a computation has occurred. If you don't have that return value, that proof, then obviously the rest of your code can't and shouldn't continue, and FP makes it obvious compared to more traditional imperative approaches.
And this idea extends into so many other things that people consider core, or at least originating from FP. Result/Option types come to mind, making the possible failure of "proof of work" explicit in the type signature, so people are forced to consciously handle it. It also leads into the whole idea of type driven design, one of my favorite articles, "Parse, don’t validate"[1], describes this as well, making types that clearly restrict and set the expectations needed for the "proof of work" to always be returned from a function.
[1] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
This assumes no contention on a limited number of bowls or having only one kitchen tool for beating or whisking etc. :P
I point that out not to demand that the metaphor be bulletproof, but because I think it may help explore something about state-handling and the FP/imperative split.
How might this tiramisu-tree model change if we were limited to N bowls and 1 beater and 1 whisk, unable to treat them as statelessly-shareable or endlessly-duplicable?
None of those things are exclusive to fp. They are just tricks developed by fp.
Here’s how to get a high level characterization of what fp actually is:
You know how in math and physics they have formulas? Formulas for motion, for area, etc.
Functional programming is about finding the formula for a particular program.
Typically when you code you write an algorithm for your program. In functional programming you are writing a formula. Think about what that means.
The formula has side effect benefits that make it easier to manage complexity and correctness. That’s why people like it. These side effects (pun) are not evident until you programmed with fp a certain amount.
Obviously though people naturally reason about things in the form of procedures rather then formulas so fp tends to be harder then regular programming.
It's always somewhat possible to express what are typically considered imperative features in a functional fashion (e.g. monad), or bend imperative languages to behave somewhat like functional ones: I think the differences become clearer once we reach out for the underlying theoretical models.
Erlang & Elixir too.
I “grasp” FP, but that pretty much sums up my experience with it. Half of its promises it delivers in the form of “you got used to read a multi-faceted inside-out mess”. I think FP is actually harmful, because it masks the fact we don’t properly teach regular programming.
> Languages like Java encourage patterns where you instantiate a half-baked object and then set the fields later.
Maybe it did before 2010. For many years everyone prefers immutable objects (so that object and its builder have different types, or in simpler case -- no setters, only constructor initialization). You can see it in pretty much all frameworks.
I'm ok with both functional and procedural languages, I just think this article is not about functionality. Come on, FP is all about monads!
Moreover, the "imperative" code example would be impossible anyway with immutable objects without side effects. So what I think the article is about is immutable data types. Everyone agrees that immutable are better, we have to do mutables only when we're optimizing for CPU/RAM.
And BTW concurrency is typically easily achieved if variables were not plain objects, but Future/Observable/Flow/Stream -- pick your poison. They all have passed the hype hill already, and became "just a tool" I think.
In FP the fundamental unit of composition is the function; your solution is expressed as a composition of functions, which are treated as first-class objects (first-class meaning, functions can be manipulated via higher-order functions, or "functions of functions"), a feature not always seen in object-oriented or multi-paradigm languages. Polymorphism is most frequently achieved through parametric polymorphism, or "generics," and ad-hoc polymorphism, or "trait bounds"/"typeclass constraints".
In OOP the fundamental unit of composition is the object; your solution is expressed as a composition of objects, whether through actual composition/aggregation (objects containing and possibly delegating to other objects [1]), or subtype polymorphism, also known as "inheritance." Parametric and ad-hoc polymorphism can often feature in OOP languages as well, but subtype polymorphism is a distinguishing characteristic of OOP.
Functions, particularly pure functions without side effects in the "real world" such as I/O or hardware access, are akin to equations in which an expression can be replaced by its value - the "left-hand side" equals the "right-hand side." Mutable state often does not enter into the picture, especially when programming in this "pure" (side-effect free) style. This makes functional programs easier to reason about equationally, as one can determine the value of an expression simply by inspection of whatever variables are in the function's scope, without having to keep track of the state of the entire program.
Objects are distinguished by their often stateful nature as they bundle together data/internal state, and operations over that internal state. Often such internal state is hidden or "encapsulated" from the client, and the internal state is only modifiable (if at all) via the object's class' set of public methods/operations. Objects with immutable internal state are more akin to closures from functional programming - that is, functions with access to a "parent lexical scope" or "environment."
Between the two extremes exists an entire spectrum of mixed-paradigm languages that incorporate features of both approaches to structuring and modelling a software solution.
[0] https://wiki.c2.com/?ExpressionProblem
[1] https://en.wikipedia.org/wiki/Composition_over_inheritance
That's not right. The difference between data and control flow oriented programming is the difference between laziness and eagerness. The difference between imperative and functional programming is largely one of abstraction, not a feature in itself.
The genuine core feature of FP is the separation of data and methods, that stands in contrast not to imperative programming but object oriented programming, whose core feature is fusion of data and methods. Functional programming tries to address complexity by separation of concerns, pulling data and methods apart, OO tries to address complexity by encapsulation, pulling data and methods into purely local contexts.
This is also where the association between FP and static typing comes from that the post briefly mentions. Pulling data and functionality aside lends itself to programming in a global, sort of pipe based way where types act like a contract between different interacting parts, whereas the information hiding of OO lends itself to late binding and dynamic programming, taken to its most extreme version in say, Smalltalk.
Related
I Probably Hate Writing Code in Your Favorite Language
The author critiques popular programming languages like Python and Java, favoring Elixir and Haskell for immutability and functional programming benefits. They emphasize personal language preferences for hobby projects, not sparking conflict.
Post-Architecture: Premature Abstraction Is the Root of All Evil
The article explores Post-Architecture in software development, cautioning against premature abstraction. It promotes simplicity, procedural programming, and selective abstraction for maintainable and efficient code.
My programming beliefs as of July 2024
Evan Hahn emphasizes tailored programming approaches, distinguishing "simple" from "easy," promoting testability through modularity, and advocating for ethical coding practices prioritizing societal impact and nuanced thinking in software development.
Functional languages should be so much better at mutation than they are
The article explores integrating mutation into functional programming, discussing methods like restricted mutable structures, local mutation, and linearity, while highlighting challenges and the need for a balanced approach.
Functional programming languages should be better at mutation than they are
The article explores integrating mutation into functional programming, discussing methods like restricted mutation, local mutation, and linearity, while emphasizing the need for user-friendly solutions that maintain functional principles.