7 ms·
Imo functional programming is one of those things that makes sense from a theoretical perspective, but comes with compromises when it comes to reality. The thi
by skohan 4y ago
Imo functional programming is one of those things that makes sense from a theoretical perspective, but comes with compromises when it comes to reality.
The thing about functional programming is that the confidence you get from immutability comes at the cost of increased memory usage thanks to data duplication. It's probably going to create a ceiling in terms of the absolute performance which can be reached.
There are just other ways to solve the problems like NPE's and memory safety, like ADT's and ownership rules, which don't come at the same costs as FP.
- simion314 4y agoFrom my experience if you are not dogmatic/extremist about it you can gain a lot, so I try to do things in a functional way in my non functional languages.
- skohan 4y agoYeah I totally agree. I think writing code which is "functional where possible" is super powerful and offers a ton of advantages. Trying to cover that last 20% of cases where you have to bend over backwards to create a performant functional solution, to solve a problem which is trivial with a little bit of mutability, doesn't make sense.
- pyrale 4y agoData duplication is coming to all minstream languages these days, whether it's a community best practice or a requirement for some libraries (e.g. streams in Java). I'm not saying that in-place operations don't have their uses, but to me, these use-cases look more and more like niche cases, as opposed to being the default choice in the past. That is well-reflected in new languages like rust, where a new name is by default immutable, and must specifically be flagged as mutable in order to be variable. It means that the benefits of immutability and the performance tradeoffs people are willing to take are evolving. I would assume larger codebases and faster hardware means performance is less valuable, and clarity comes at a premium. I do agree with you that NPEs and memory safety are unrelated problems, though.
- skohan 4y agoData duplication is a tool which can be used to avoid large classes of bugs, but there's no question it comes at a cost to performance. If anything, I would say explicit mutability allows us to decrease data duplication. By being explicit about what's mutable and what's not we don't have to resort to holding "safe copies" to ensure shared memory is not being mutated unexpectedly.
- dottedmag 4y agoImmutability does not even have to be bound to functional programming. One can use persistent (= immutable from the user's perspective) data structures, like immutable.js and get most of the benefits. Moreover, persistent data structures can be optimized well (see Clojure), so that the performance issues may be relegated to the inner loops, as usual, and the rest of the program may use safe-for-sharing data structures.
- ajuc 4y agoMy problem with functional programming is refactoring. If you don't have side effects when you want to do 2 things deep into the call tree to something that is in completely different call tree branch - you have to extract it all the way up to the common parent and pass it through all the intermediates just so that one function deep there can access it. It's incredibly frustrating when you work in a functional language, and yet it's the main benefit (no side effects). I'd like to have a language that is imperative when written and functional when read :)
- mejutoco 4y ago> I'd like to have a language that is imperative when written and functional when read :) Depending on which language Monads give you exactly that bridge between imperative and functional. In your example, you can always choose to have side effects in that deep call. Personally, I like a type-driven approach. Then, I do not care so much where functions are (they will be grouped logically, but could be anywhere), as long as the type in and the type out matches.
- iterati 4y agoI've handled this (in Clojure) either by passing a context map through the entire stack or binding some context atom at the top and using it lower down the stack. The binding is less obvious at first glance, so I prefer to pass the context, but both make testing quite easy and reduce the need for heavy refactoring.
- beders 4y agoI like that aspect because it gives you a hint that your call tree is not how it should be. Lifting out side-effects and passing the data back in is one approach, but not the only one. Turning a deep call tree into a flat pipeline is a popular other approach and often leads to less complexity, better compos-ability.
- ajuc 4y agoI like it when I'm done. I very much dislike it when I'm not sure yet what my code should do so it changes constantly. And that's most of the time in gamedev for example.
- Widdershin 4y agoThis is actually an area where there’s room to improve FP languages. If you track ownership in functional languages, you can statically determine if a value is used more than once. If it’s only used once, you can apply any updates to that value in place without allocating more memory. This gives the performance benefits of mutability with the safety benefits of immutability, in some common cases. The main trick is adjusting memory layouts accordingly. You can keep it simple by only applying this optimisation for functions of A -> A, or if you’re replacing it with a different type you can analyze the transformations applied to a value and pre-emptively expand the memory layout. If a value is likely to be used only once, but might be used multiple times, you can also apply the same approach at runtime by reference counting and updating inplace when there’s only a single reference (for functions of A -> A at least). I believe the Roc folks are aiming to have aspects of this functionality, and I also believe there’s similar stuff becoming available in Haskell under the guise of linear types. Finally, if you really need a shared mutable value, that can be achieved with mechanisms like the State type in Haskell. In short, the pieces are there to create a functional programming language that doesn’t introduce needless memory usage overhead, but I don’t think anyone has put all the pieces together in a convenient and accessible way yet.
- bcrosby95 4y agoI get that not everyone does, but a large part of why I use Clojure is because it makes a whole class of concurrent designs easier. In particular, sharing that immutable data across multiple threads. As a simplified example: one thread modifies the data, and another thread writes a snapshot of the data. In the programs I write, it would pretty much never benefit from this optimization.
- colonwqbang 4y agoWhy do you think that functional programming results in data duplication? I would think it's rather the opposite. With strong immutability like in Haskell, you can share values even between threads and can avoid defensive copying. Two versions of the same immutable tree-like data structure can also share part of their representation in memory. (Haskell has other problems causing increased memory usage, but not related to data duplication in my mind)
- Widdershin 4y agoNot the poster you’re responding to but I think they’re referring to the current need to allocate more memory when updating immutable data structures. Not that there aren’t ways to represent mutability in Haskell, just that the de facto use of immutability causes excess allocation.
- colonwqbang 4y agoEfficient functional programming often uses tree-like data structures. These can be immutable but still avoid duplication. Consider if you "duplicate" a Data.Sequence Seq (finger tree) for modification. You're not actually copying the whole structure, you are creating a new root node and re-using as much as possible of the common structure. The end result is that a bit more memory is used in the simplest case, but not due to duplication I think. The benefit is that a thread can make a modified value at cheaper cost without affecting another thread that is still using the original value. I also think it's easier for the programmer to understand the code.
- AnimalMuppet 4y agoThat's duplicating part of the structure. That uses more memory than just modifying a value in-place, but less than duplicating the whole tree.
- colonwqbang 4y agoSure, but let's assume that the program has more than one thread and that another thread could still be using the old value. In that case, an imperative program might be required to copy the whole structure or sleep until the existing users are done, which is often less efficient and is always more complicated. If it's ok to support only a single concurrent user of the value, then a mutable structure is indeed more efficient. Even in Haskell we have mutable structures that can be used for such purposes. The interesting question to me is, what should be the default? I think there is a good argument that it should be the safer, simpler immutable structures.
- louthy 4y agoIf the first thing you talk about is performance and not quality and maintainability, then you're already missing the point. Most software just isn't in some super high-perf environment - what matters is fewer bugs, easier maintainability, better communication with other engineers (through declarative code). The code we work on in the 2020s is much, much more complex than code written 20 years ago. We need better primitives to help our weak and feeble brains deal with this complexity. FP (particularly pure FP) gives us that. It isn't a panacea, but it's a major step in the right direction.
- skohan 4y agoI just disagree that performance isn't a concern in almost every context. There's a hierarchy of concerns, to be sure, and if you haven't written reliable code which solves the problem yet you shouldn't be worried about performance, but if your PL itself imposes a performance tax, that's something which has to be paid every time your program gets executed, by every user. As programmers, our job is to not to play with abstractions, it's to move electrons and make hardware do things. We can't afford to abstract away the complexity of the hardware completely. Indeed the trends in PL popularity of the past 20 years have been to move back closer to the hardware, and away from highly abstracted environments, like scripting languages and JVM.
- tluyben2 4y agoBut Python/Ruby (and JS? Not sure how far they are with optimising that or what the comparison would be) are very slow compared to Haskell. So people are paying that price all the time without getting any benefits that Haskell (etc) can over next to it. I agree with the GP; performance is really not very interesting for most projects and most (Py/JS are the top dev languages by far I think) programmers/companies are agreeing with that by using low performance environments that make them productive. So productivity seems to win out. For sake of the environment and hardware upgrades, I think we definitely should make an effort and we can see that improvements in compilers and PL theory do help with this when the goal is practical programming languages using these techniques; Rust does, Haskell was meant as academic language for a long time. I think robustness/security should go first anyway as hierarchy of concerns; that's where things are really breaking now.
- yobbo 4y agoThe extent to which immutability leads to duplication seems a matter of implementation rather than a principle. The compiler/runtime could optimize such that memory is reused, as long as all other laws are obeyed.
- UncleMeat 4y agoTo some degree. But it really is the case that a persistent functional data structure is going to have a slower insert operation than a traditional mutable set. There's no getting around that.
- savingsPossible 4y agohttps://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Sequence.html https://hackage.haskell.org/package/containers-0.6.5.1/docs/... log(n) slowdown to add in the end but the cost to add in the middle is cheaper then the trivial array (see insertAt)
- UncleMeat 4y agoThe asymptotic behavior is not the entire story. Because persistent data structures almost necessarily need to have their data allocated non-contiguously they have terrible cache performance and prefetching/speculation behavior in comparison to data structures that take advantage of contiguous memory locations. I also mentioned sets, not lists.
- savingsPossible 4y agoNot that this answers all your objections (it does not, caching might be a problem!) But sets are also a mere log(n) away https://hackage.haskell.org/package/containers-0.6.6/docs/Data-IntMap-Lazy.html https://hackage.haskell.org/package/containers-0.6.6/docs/Da...
- brabel 4y agoThis article tries to push FP as a solution to NPE??? What the?! NPEs are a problem due to dodgy type systems... it's a type sytem problem, not a language paradigm one... i.e. it has no relation to whether a language is functional or not. For example, Dart 2 is null-safe! No one in their right mind would claim Dart is a FP language. Even Java can be null-safe if you use some javac plugin like the Checker Framework. Also, a language can totally be functional and yet suffer from NPE, like Clojure or Common Lisp, but I suppose the author may be forgiven here because they are talking only about "purely functional programming languages"... (they didn't mention "statically typed" though, but that's clearly implied in the content)... I believe the author is inadvertently pushing for two things that are pretty much unrelated to FP, even if they are a requirement in most purely-functional languages: * immutability * strong, static type systems I would mostly agree with both (except that local mutability is fine and desired, as anyone trying to implement a proper quicksort will tell you - also, see the Roc language[1], which is purely functional but uses local mutability), but I write my Java/Kotlin/Dart just like that and I wouldn't consider that code purely functional. I don't think whether the code is purely functional actually matters much at all compared to these 2 properties, which can be used in any language regardless of their main paradigm. [1] https://www.roc-lang.org/ https://www.roc-lang.org/
- narrator 4y agoThe other problem with functional programming is it's harder to look at code and figure out the time complexity. At least with non-functional languages I can easily figure out why there are performance problems with it. Stuff like lazy evaluation may seem cool, but it's not when you have to figure out why something's slow.
- tasuki 4y agoThis is lazy vs strict, not functional vs whatever. I'm a fan of functional programming, not so much a fan of lazy evaluation. Look at PureScript or Idris perhaps?
- nequo 4y agoIn addition to sibling’s recommendations, consider OCaml too for a strict language that is vaguely similar to Haskell and performs similarly well in benchmarks: https://cs3110.github.io/textbook/cover.html https://cs3110.github.io/textbook/cover.html
- brmgb 4y agoThat's an Haskell issue not a functional programming issue. Haskell is pretty much the only functional programming language which is lazy by default. It's the sole one for the reason you give. Every other ones are eager by default and you can opt in to lazy evaluation when needed.
- sesm 4y agoDuplication problem is solved by immutable data structures with structural sharing. They are part of Clojure standard library and exist in some other languages.
- ParetoOptimal 4y ago> Imo functional programming is one of those things that makes sense from a theoretical perspective, but comes with compromises when it comes to reality. Ironically whay you say is true in theory, but not true in the real world. Source: real world Senior Haskell programmer