21 ms·
What's functional programming all about? (2017)
- mgdev 2y agoI'm personally a fan of FP. It offers clear benefits: simplified parallelization, improved testability, and reduced side effects. These advantages often lead to more maintainable and robust code. 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.
- eyelidlessness 2y agoAlso worth mentioning in terms of mixing functional/imperative techniques: it can be very helpful to use languages (and frameworks, libraries, interfaces) which are functional-first/-by default, not necessarily pure but which provide specific affordances for managing side effects. This can be seen in languages like Clojure (with reference types distinct from most of the rest of the language/stdlib). It’s also a hallmark of many projects with a reactive paradigm (which in some form or another have dedicated APIs for isolating state and/or effects). These aren’t strictly necessary for effectively using functional techniques in an imperative environment, but they can go a long way toward providing useful guardrails you don’t have to figure out yourself.
- VirusNewbie 2y ago>d. As a result, FP's emphasis on granular transformations can increase cognitive load, particularly when dealing with these intricate dependencies. Does it increase cognitive load, or is it just making the cognitive load more apparent. Sure it's easier to write multithreaded code if you assume race conditions can't happen, but that's not actually accurate to what would happen. Perhaps FP just makes explicit in the typing/coding portion, what would otherwise be uncovered hours/days/weeks later in a bug?
- wredue 2y agoIt offers none of those things and provably doesn’t have more robust code. Maintainable code? I dunno. From what I’ve seen, FP is much worse for changing when you have shit deep in your call stack. I wouldn’t call that maintainable. More readable? Nah. Nearly everyone has a much easier time consume code when they’re not constantly context switching between function jumps all over the place. Additionally, FP only makes threading “easier” in one very specific circumstance. Once you need to parallelize the same workload, FP is generally much much harder to thread, whereas this is trivial in other languages.
- kagakuninja 2y agoI don't know exactly what you are trying model with this hypothetical circular dependency. However, circular dependencies can be represented with lazy (aka non-strict) references and deferred function calls (aka thunks / call-by-name), and are IMO easier to reason about than mutable imperative techniques for representing such relationships. They also have the advantage of being totally thread-safe. The OP (Lihaoyi) is the author of an important set of Scala libraries, and Scala is an example of what you are describing. Scala is a hybrid OO / FP language that is not dogmatic about purity. You can be totally imperative if you want. It is common in the Scala ecosystem to implement performance-critical library code using local mutability and null references. Internally the function is imperative; to the caller, it is functionally pure.
- lihaoyi 2y agoAuthor here. This blog post is actually kind of funny; I had a flash of clarity one afternoon that wouldn't go away so I spent 8 hours in a manic frenzy banging it out in one pass with no editing. Not how most of my writing happens (typically its a tedious slog with multiple passes of editing and refinement) 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 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
- carapace 2y agoHave you read Backus' original FP paper "Can programming be liberated from the von Neumann style?: a functional style and its algebra of programs"? https://dl.acm.org/doi/10.1145/359576.359579 https://dl.acm.org/doi/10.1145/359576.359579
- binary132 2y agoHi Li, appreciate your work. How do you feel the state of Scala is these days? I took the EPFL intro on Coursera years ago, but I was always disappointed by two things: the community feels very fragmented outside of IDEA (RIP ENSIME — oh, is it back now?), and it seems like Spark completely overwhelms the rest of the Scala ecosystem. I’ve mostly moved on these days but still think fondly of it from time to time.
- Barrin92 2y ago>The core of Functional Programming is thinking about data-flow rather than control-flow 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.
- keybored 2y agoNow you’re just defining FP as a contrast to OO. That’s wrong and boring.
- breadwinner 2y ago> The genuine core feature of FP is the separation of data and methods Couldn't disagree more. Based on this definition C language would be the epitome of functional programming... but it is not.
- giovannibonetti 2y agoThat's exactly what the parent comment said, since C is an imperative programming language: > 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
- randomdata 2y ago
- gr4vityWall 2y agoThe 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.
- codr7 2y agoFP doesn't have to be a religion; in Common Lisp it's just an idea, an ideal to aim for perhaps.
- iLemming 2y agoSame 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".
- gleenn 2y agoAs 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.
- leoff 2y agoInteresting how the post doesn't mention the word "side effect" once. To me, all of this could be summarized by "no side effect".
- tromp 2y agoHe didn't need to mention it because it's implicit in data flow. Instead of the side effecting / state changing whip(cream) that needs to be under flow control, you have whipped_cream = whip(cream) describing the flow of data. Data flow describes the relationship between non-changing entities and thus there are no side effects. While they could have mentioned it, it wouldn't really change the message.
- Feathercrown 2y agoI think the point of the article is to illustrate why "no side effect" is important.
- magicalhippo 2y agoWhile I agree, you can get all of that FP example from the article in say C++ by liberal const-usage. So is "const-y" C++ functional programming?
- fhasdfyuasd 2y agoI think liberal const-usage breaks move optimizations so it's not used in practice.
- magicalhippo 2y agoPerhaps not these days, back in pre-C++11 days it was very liberally used. And something I miss in other languages, precisely because it allowed for much easier reasoning.
- mrkeen 2y agoIt's not enough to take out the 'bad bits' - you have to put in 'good bits' too. For example, Java collections are mutable, but enough ink has been spilled about the dangers of shared mutable state, that there are various recommended defences, e.g. make defensive copies when you return collections to a caller. Or use one of the immutable collections that will throw a runtime exception if someone tries to mutate it. Now consider if you wanted to evaluate 3 + 5. Should you throw an exception which tells the caller off for trying to change the value of 3? No, the caller just wanted 8! This is what's missing with the 'just make things immutable' approach to FP mimicry. I want to be able to combine collections together. The Java standard library Set<> still doesn't have union and intersection for christ's sake.
- dang 2y agoRelated: What's Functional Programming All About? - https://news.ycombinator.com/item?id=15138429 https://news.ycombinator.com/item?id=15138429 - Aug 2017 (139 comments) What's Functional Programming All About? - https://news.ycombinator.com/item?id=13487366 https://news.ycombinator.com/item?id=13487366 - Jan 2017 (2 comments)
- deleted 2y ago[deleted]
- giovannibonetti 2y agoI wonder if there is a Python PEP for adding a pipe operator |> to the language. This could be pretty handy, as described in the article.
- mbivert 2y agoI believe that a good definition of the "core" functional programming is: it's a practical way of using the λ-calculus. Similarly, imperative programming is a practical way of using Turing machines. 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.
- deleted 2y ago[deleted]
- yodsanklai 2y agoAs a programmer, I don't know if it's still relevant to make a strict separation between programming paradigms. You can use immutable types, pure functions, closures and so on in most languages. Conversely, you can define mutable types and imperative code in most functional programming languages. 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...)
- acchow 2y agoMonads are pervasive: async-await. We just don’t call them monads.
- bazoom42 2y agoHow is async-await monads? Isn’t it just syntax sugar over callbacks?
- ryandv 2y agoConsider async-await a syntactic sugar over Promises (from JavaScript). Then, Promises constitute an instance of the Monad typeclass where monadic `bind` or (>>=) is `Promise.then()`, and `return` is `Promise::resolve()`. Here is a translation of a modification of the example given in [1]: const promise1 = Promise.resolve(123); promise1.then(v => v * 2).then((value) => { console.log(value); // Expected output: 246 }); into Haskell: ghci> let promise1 :: IO Int = return 123 ghci> promise1 >>= (return . (* 2)) >>= print 246 One key discrepancy worth pointing out is that in the `Promise.then()` API of JavaScript, the function provided to `then` (e.g. `v => v * 2` above) is implicitly composed with a call to `::resolve` in order to turn that function's pure return value, the `Number` 246, into a Promise resolving into the `Number` 246; in Haskell, this operation must be made explicit (hence the composed function `return . (* 2)` passed to the first application of (>>=) or `bind`). You could say that the instance method `Promise.then()` expects an argument of type (a -> b), with the return value of type `b` being implicitly and automatically wrapped into a Monad `m b`, whereas Haskell's bind expects an argument of type (a -> m b) on that operator's right-hand side, with the return value explicitly wrapped into a Monad `m b` by the provided function argument itself. [0] https://wiki.haskell.org/Monad https://wiki.haskell.org/Monad [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- ryandv 2y agoThe classic dichotomy drawn between functional programming (FP) and object-oriented programming is the "Expression Problem" [0]; in the former approach you optimize for extensibility of the number of operations in your model, at the cost of reduced developer ergonomics when attempting to extend the number of types you can operate upon. In the latter, object-oriented approach, you have the inverse tradeoff. 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 https://wiki.c2.com/?ExpressionProblem [1] https://en.wikipedia.org/wiki/Composition_over_inheritance https://en.wikipedia.org/wiki/Composition_over_inheritance
- yCombLinks 2y agoMy problem usually appears when rather than clearly laying out the types of each returned value like in the article, all the FP guys I've worked with want to build giant chains that look like : return beat(whip(mix....(eggs)))
- myth2018 2y agoMine too. I've worked for a shop adopting this practice some time ago. A very common pattern was to declare a relatively large Python dictionary with function calls and list comprehensions nested deep in the dict [sub]properties. Nice to glance, terrible to debug and reason about.
- mrkeen 2y agoLikewise with the OO guys. Eggs.mix() .whip() .beat(); There's even a term for it - 'fluent API' - described as: > Fluent API is a way of implementing object oriented API in a way that it provides readable code. [https://www.progress.com/documentation/sitefinity-cms/for-developers-use-readable-code-with-the-fluent-api https://www.progress.com/documentation/sitefinity-cms/for-de...]
- ninetyninenine 2y agoIt’s hard to characterize what fp is. A lot of people think fp is a bunch of techniques like map, reduce, immutability or high level functions or monads. 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.
- dxbydt 2y ago> Functional programming is about finding the formula for a particular program. Can be disproved trivially. Anyone can code up a program that generates the nth prime for some n, by iteratively accumulating n primes starting from 2 using trial division. otoh, an actual formula that produces the nth prime would be an earth shaking event.
- KolenCh 2y agoYou are talking past each other on what formula means. In FP, a recursive function is a valid “formula”. P.S. I’m not arguing for that characterization.
- ykonstant 2y agoThere exist many formulas that encode the program you mentioned or other sieving methods, so it is unclear what you mean by "actual formula". See https://en.wikipedia.org/wiki/Formula_for_primes https://en.wikipedia.org/wiki/Formula_for_primes
- dxbydt 2y ago
- kh9sd 2y agoVery nice article, I liked it a lot! It personally resonated with me and my own conclusion that the core "benefit" of FP is (for lack of a better work, stupid Bitcoin) "proof of work". 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-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
- Terr_ 2y ago> Anything vertically separated can be done in parallel. 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?
- williamcotton 2y agoSounds like you need a Kitchen Monad!
- mrkeen 2y agoYou're describing (safe) locking of resources, best put forward by Dijkstra in 1965 as the Dining Philosophers problem. I have yet to see a more practical, elegant solution than Haskell's STM for this: https://www.adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-In-Pictures.html https://www.adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-...
- deepsun 2y agoThe article is not about FP. > 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.
- dianeb 2y agoDefine what you mean by "everyone" -- there are times where the cost of immutability can be overwhelming, such as in high traffic systems with overly complex data structures which you are required to use because someone who should have known better insisted upon writing. (sorry, bitter personal experience) And yes, that is explicitly "modern" Java code written by a lead engineer and "java champion" in 2023.
- deepsun 2y agoYes, I feel you. As I said, we often need to sacrifice immutability to performance, and that's ok. If they insisted using immutable structures in high-performance applications, then functional programming won't help anyway.
- fhasdfyuasd 2y agoHopefully we won't have to make that trade off for too long https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf https://www.microsoft.com/en-us/research/uploads/prod/2020/1...
- dboreham 2y agoMissed the key point: FP serves to make someone feel like they're smarter than someone else.
- phplovesong 2y agoSounds like a take from an individual who a) does not see the benefit or b) just does not get it. I have refactored large enterprise systems, and always tend to write in an FP style depending on the language. Im not talking about full blown Haskell here, but traditional C like languages. Just keeping it simple, avoiding mutation, isolate side effects and having pure functions will take you a LONG way for better software. You dont have to go balls deep in category theory for FP, and usually the language you use is not really suited for a monadic way of things, i dont like to shoehorn that stuff in if im not working with something like ocaml etc.
- fulafel 2y ago> there are probably just as many people using FP without static types: in some parts of the Javascript community, Clojure, Scheme or one of the many other Lisps. Erlang & Elixir too.
- wruza 2y agoIt might look like a bit of a mess, but if you look carefully, you will see 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.
- Vinnl 2y agoWhile I agree that that's the case for the promises in many such tutorials, I think this article explicitly shows that that isn't inherent to the thing that FP is about. Your quote is specifically about the code formatted in such a way, with arrows drawn all over it, to match the box diagram. But if you look at the code as it would actually be written, in a functional style: def make_tiramisu(eggs, sugar1, wine, cheese, cream, fingers, espresso, sugar2, cocoa): beat_eggs = beat(eggs) mixture = beat(beat_eggs, sugar1, wine) whisked = whisk(mixture) beat_cheese = beat(cheese) cheese_mixture = beat(whisked, beat_cheese) whipped_cream = whip(cream) folded_mixture = fold(cheese_mixture, whipped_cream) sweet_espresso = dissolve(sugar2, espresso) wet_fingers = soak2seconds(fingers, sweet_espresso) assembled = assemble(folded_mixture, wet_fingers) complete = sift(assembled, cocoa) ready_tiramisu = refrigerate(complete) return ready_tiramisu ...then surely you would agree that that doesn't look like a mess at all, compared to the imperative version a couple of lines below it? And yet, it brings all the benefits described in the following paragraphs!
- wruza 2y agoThere’s nothing particularly FP about it though. A function taking a value and returning a new value is as “FP” as a good deed is “Christian”.
- Vinnl 2y agoThat's mostly a discussion about semantics, which isn't that interesting IMO. It demonstrates the use of pure functions that avoid side effects, and argues that this is how you can get some important benefits associated with functional programming without having to "read a multi-faceted inside-out mess". If you have a definition of FP that intrinsically includes that mess, then sure, maybe that's harmful, but that seems orthogonal to the article in question.