14 ms·
Why I Prefer Functional Programming
- barry27 6y ago"I prefer functional programming because I have decided to prefer it".
- karmakaze 6y agoI like FP's other "timeless" feature, you can think of the operations without respect to time, there isn't a sequence of operations, it's just a relation between the input and output. It may be described sequentially in parts but there is no mixing of output with input so can be thought of happening instantaneously. This is also the same reason why I like SQL, it describes properties of the result set--if your thinking of it as procedural steps it's more difficult. When it comes down to performance tuning however, you do have to consider what really happens in terms of index choices, loop nesting, etc. I never got into FP problems large/complex enough to hit this point where I have to understand how FP sausages are made.
- marcosdumay 6y agoHaskell is famous for having problems with space leaks, where the runtime just refuses to evaluate your code and keeps storing more of it to run later. It's not usually a problem because people will teach you how to avoid most of it on any introductory text (usually, it's not hard), but if no care is taken, it will happen. To go into another timeless paradigm, logical programming is famous for unexpected exponential execution times. It's rare that those get past tests, but it's something that developers are constantly having to fix (this or ensuring the type system detects determinism). SQL has a much milder version or this with underspecified joins. Getting ride of time makes a lot of things easier, but is not trouble-free.
- samcal 6y agoI really like that distinction, relating imperative/FP tradeoffs to the classic space/time tradeoff. I'd also point out that betting on space getting cheaper over time seems like a better bet than betting on time getting cheaper :)
- marcosdumay 6y agoThat's not the trade-off. The trade-off is about what you have to think about when programming, and what kinds of bugs appear. Non-buggy performance follows different rules.
- UncleMeat 6y agoIs time better? My experience with Haskell went like this. 1. Interested in Haskell. Learn the language, read papers, enjoy the PL concepts and interesting academic work. 2. Does it scale? Implement small program in in C++ and Haskell. Optimize as best I can. Talk to people who work on ghc who happen to be in my lab. Further optimize. Everybody agrees, no further clear way to optimize the Haskell program. C++ implementation remains several orders of magnitude faster. If it were a time and space trade off, laziness would lead to good performance. But it doesn’t. It tends to produce stupendously bad cache behavior. Haskell is a lovely platform for understanding some recent PL theory. It is possible to write the sort of obviously correct programs that make it so desirable. It isn’t resource efficient. Worse, laziness means that performance tends to be chaotic. Small seemingly unrelated changes can change the execution order and change your profile a lot. Similar challenges exist in logical programming.
- YeGoblynQueenne 6y ago>> To go into another timeless paradigm, logical programming is famous for unexpected exponential execution times. I have never heard of those famous "unexpected exponential times" in logic (not "logical") programming. Could you please give an example of what you mean? I'm also not sure what you mean by "this or ensuring the type system detects determinism". What logic programming languages with a type system do you mean? Why would a type system be required to detect determinism?
- carapace 6y agoYou might like Prolog.
- karmakaze 6y agoI've tried Prolog. It required thinking in both the specification correctness and it's 'search/match' execution based on how it's specified (with manual path cuts inserted as needed) much more so than these FP languages. Effectively writing a procedural definition non-procedurally. SQL can be like this too but is a small space/number of cases that it's not so unexpected.
- amw-zero 6y agoThis mapping of inputs to outputs is only superficial and hides that fact that computation is inherently “sequential” as you say. That’s why the even lambda calculus requires reductions. Of course I prefer writing in a functional style, because pure functions are just simpler. But all of computation can’t be reduced to just pure functions, which is why even Haskell has to call out to its runtime eventually.
- dragonwriter 6y ago> But all of computation can’t be reduced to just pure functions, which is why even Haskell has to call out to its runtime eventually. All of computation provably can, but not everything computers do, despite the name, is computation.
- amw-zero 6y agoComputation cannot be reduced to simply functions, that’s why we need something like the lambda calculus to express computation. There are functions within the calculus, but to say that “computation is a function” is not correct.
- pcstl 6y agoActually, computation, in the classic sense used by Turing, Church, and other pioneers, refers to evaluating the value of (mathematical) functions. The word computation first appears in the earliest papers of the field which would later become computer science referring to "computable functions", which refers to functions which can be evaluated by a mechanical process. Hence saying "not everything computers do is computation".
- zupa-hu 6y agoSo he prefers the functional programming style based on superior portability. While I too mostly prefer functional programming principles, the argument seems weak to me. Even if those features are timeless and programs written with those can be ported to most other languages, it doesn't follow that an extra feature (even if invented in the future) won't make you 10x more productive thus desirable. I prefer the functional programming style (in a non-FP language) for the productivity gains. I move faster, the codebase has a smaller footprint 'in my brain', it's easier to reason about it, so overall I'm more productive when using it.
- Syzygies 6y agoFunctional programming appeals to many of us for reasons other than these practical considerations: Functional programming feels like reasoning in algebra. As in Modern Algebra for math majors (groups, rings, fields) and beyond. There's a saying in mathematics that when any field matures it turns into algebra. Life crossed a threshold from chemistry to biology on Earth, and developed exponentially from there. Order in mathematics crosses a similar threshold, as it becomes sufficiently structured to support algebraic reasoning. The subjective experience is like ice melting into a churning liquid, or a land-locked creature learning to fly. Once one has this experience, one cannot imagine thinking any other way. In the case of functional programming, programs written in other languages feel like ad hoc pre-civilization constructions, doing arithmetic by counting pebbles. Advocates of Haskell don't tend to express this, because from the outside it can come off like trolling, but this algebraic sense of wonder is at the core of many Haskeller's experiences. We all have the example of Lisp in our minds, its "we found God" advocacy did much to hinder its adoption. Nevertheless, understanding this explains much about Haskell. The real point of lazy evaluation is that it best supports this algebraic reasoning, as carbon best supports life. The 47,000 compiler options reflect a primary goal of being a research bed for language constructs derived from mathematical category theory, despite its success as a practical language for those free to choose it. The killer app for Haskell is parallelism. To this day it has the best implementation of parallelism; one can achieve a 7x speedup on 8 cores by adding a handful of lines to a program. This ease is a consequence of the functional model, and of considerable effort by Haskell's developers. Idris 2 is itself a joy to learn, if one wants a smaller, cleaner Haskell without the 47,000 compiler options. One gets to learn dependent types. Alas, it doesn't offer commercial-grade parallelism.
- __jem 6y ago> Life crossed a threshold from chemistry to biology on Earth, and developed exponentially from there. Order in mathematics crosses a similar threshold, as it becomes sufficiently structured to support algebraic reasoning. It's funny, because I think this metaphor cuts exactly the opposite direction -- code reaching sufficient complexity, the pure math approach doesn't survive an encounter with the real world. I love static analysis and think that there are core business domains that are best expressed with a rich type system, but there is a whole world of absolutely critical programming that cannot simply eliminate messiness, due to interfacing with humans and our flawed practices. With sufficient time and an expressive enough type system, even the messiest business process can be represented, but I'm not convinced that's an example of beauty but rather stubbornness.
- blunte 6y agoThe author presents succinct and compelling reasons to prefer functional principles. To add, I find the strong push for purity of functions helps free up the part of my mind which seems to constantly worry about potential problems or unknown/unexpected behaviors. With that background noise out of the way, I can create more elegant (by my own judgement :) ) solutions to needs. I also sleep better.
- loxs 6y agoI agree to a great extent with the article, except for the dislike for mutability. I used to be like this (dislike it), then I found Rust. Now I prefer imperative, eager computation with (safe) mutability and even some lightweight OOP (the way Rust does it with Traits/Type-classes). It seems that it much better reflects the real world. Rust makes the mutability concerns obsolete, as you can have safety in the imperative/mutable world. Otherwise Rust is very much functional in all the great ways explained in the article.
- dudul 6y agoImmutability is not about safety (not only i guess). It is about having code that is easier to reason about.
- loxs 6y agoYeah, with Rust lifetimes it's much easier to reason about, compared to eg Java. Also, in something like Haskell it's not that your data does not change. It does change and people employ really complex idioms to facilitate that. It might be "easy to reason about", but it's definitely not easy to grasp. Lots of people find it much easier to just mutate something (hence the popularity of imperative languages). Rust takes best of both worlds. You can both mutate, and you are sure that this is compile-time guaranteed, so you can't just try to mess with a value that this part of the code does not own.
- bionhoward 6y agoI’m super interested in FP for AGI, but one issue with such setups (see Jax.experimental.stax) is the neural nets are parametric functions, and if recurrent, can have state, too. That adds up to a lot of extra work for a programmer to manually pass around these parameters. It’s doable but also distracting from the real work of cognitive architecture, which is why I switched from stax style to Flax. Flax is currently by far my favorite neural net library, despite being pretty small, because it lets you write a neural net module as a function and decorate that with @nn.module — the decorator wraps your function in a class instance to manage the params and state. This can shave 10 lines per module, and if you’re like me, you have like 50 bajillion ideas for different neural net modules, it really adds up. TLDR: Check out Flax if you like functional programming and neural networks!
- bachmeier 6y agoRequiring a functional programming approach when you teach makes your life much easier. I teach a computationally-intensive course for advanced economics PhD students. These days I have them think carefully through their problem, set up a recursion, and make one call to reduce. The vast majority of problems fit into this framework. I no longer have to work my way through 500-line monsters that look like entries in an obfuscated Perl competition. I read a few lines of code, I know exactly what it does, and I get on with my life.
- mattmanser 6y agoYou can do this in any language, functional or not. Every language has recursion, every language has a simple one liner way to do reduce. They have generally had them for over a decade now. I don't know if it's the case any more as I mainly work alone, but I often saw new programmers had been taught about recursion, but didn't actually understand it. So they never used the concept until you pointed it out, even if it could vastly simplify their code.
- willtim 6y ago> You can do this in any language, functional or not. No, not in practice. For example, in an average imperative language, you'd have no tail-call optimisation, and so would run out of stack space pretty quickly. There'd also be no persistent collections, meaning you'd have to keep copying any sets of values for every recursive call with absymal complexity. And so on...
- deleted 6y ago
- curryhoward 6y agoI like to think there is a nice pun here, that functional programming is "timeless" in two simultaneous ways: (1) As mentioned in the article, values are immutable and side effects are reified so you do not have to reason about time. Instead, you can reason equationally, which unlocks some nice benefits related to refactoring, reasoning about correctness and concurrency, testing, reproducibility, etc. (2) Functional programming is based on well-behaved mathematical foundations and so is more stable than the other programming paradigms. Object-oriented programming is a constantly moving target because it doesn't have a principled formal foundation (despite many attempts to establish one on an ex post facto basis), whereas functions are "timeless" in the sense that they're here to stay. It's not without tradeoffs of course, but if you're willing to have an open mind about how computers should be programmed and forego some things that are so pervasive in traditional programming (e.g., unrestricted side effects), the payoff is huge.
- sidpatil 6y agoThis is known as combinatorial logic, contrasted with sequential logic which is "timeful".
- nendroid 6y agoI disagree with the higher order functions thing. Writing functions that receive other functions as input can lead to over complicated code that's really hard to read. It's literally the same thing as dependency injection just with functions instead of objects. Use sparingly. I would avoid altogether except for common ones like map, reduce and filter. Functional programming promotes the idea of composition of morphisms and point free programming. I would use this in place of Object composition/dependency injection/higher order functions or whatever you want to call it.
- anon_d 6y agoWriting functions that receive other functions as input can lead to over complicated code that's really hard to read. I work in FP languages, and I find this type of code extremely intuitive and easy to understand. I suspect that you are just not used to thinking this way, and that with more familiarity, you would not have this opinion.
- nendroid 6y agoSo you like writing functions that take 3 functions as input parameters and returns a new function that also takes a function as an input parameter and returns another function? In general excessive use of this at great depth leads to code that is not only less readable but less modular. The complexity of higher order functions can easily be reasoned about by looking at the cardinality of the type. Sum types and product types are standard and easy to reason about. However an exponential type that takes in an exponential type and returns another exponential type leads to cardinalities and possibilities that are much harder to reason about. Composition of combinators is a pattern promoted by point free programming that leads not only to higher readability but greater modularity due to the reduced cardinality. Additionally cardinality of the entire type can he reduced to only analyzing the the final result of the composition. F = A . B . C The cardinality of F is all that needs to be known. The cardinality of the individual components can be ignored and you don't have to reason about this. This is not the case for higher order functions. In general business problems that we deal with do not Require higher order functions. Higher order functions share isomorphisms with two concepts we are already familiar with: frameworks and metaprogramming. Frameworks are programs that call your code and metaprogramming is code that writes and calls code. These things are no different then a function that takes another function in as a parameter and calls it. All three concepts are aspects of the same thing. Therefore all the downsides of metaprogramming and all the downsides of frameworks are applicable to higher order functions. Most of the programming problems we solve in the real world do not require metaprogramming. It can easily be avoided and solved with much more composition of combinators. Again, not saying to avoid the usage completely but the usage of higher order functions like metaprogramming should be minimal and used sparingly.
- caryd 6y agoI think it all comes down to one question, do you need objects or not?
- josephcsible 6y agoWhat functionality can you only get with objects, and not with anything that is part of the FP paradigm?
- jariel 6y agoFor things that naturally have complex statefulness ... like User Interfaces. HTML DOM as a crude example, is a big 'tree of state' whereupon OO would lend well to the nature of the noes on that tree for example. When you're crunching data and the entire process is literally some kind of transform FP I think more intuitively maps to the situation. On the DB side, there is a niche of OODBs that exist and used particularly in networking because the persisted state there is really topological, in which case it doesn't always lend well to RDBMS.
- quickthrower2 6y agoAnd also what is an 'object'? Haskell has a decent system for building up complex data structures. You can write functions for those. Those data structures + those functions are kind of "an object" from the OO world (data + methods). The differences are: * Inheritance - well Haskell has many kinds of polymorphism so it doesn't need it. * Hiding data - well Haskell can hide data using smart constructors, you don't need an explicit private keyword * Interfaces - covered by typeclasses but they do a lot more! Also without any of the above you can create object-like things with just closures. If a scope has access to 3 variables in scope, it's "kinda" like an OO object with 3 member variables. You can create new scopes in a for loop, which is like creating many new object. It's a stretch but the underlying concept of something owning other things is still there.
- ufo 6y ago> Those data structures + those functions are kind of "an object" And in this setting, the type of that datastructure containing the functions can act as an interface. A trap that OO programmers sometimes fall into with Haskell is to try to emulate OO interfaces using existential types + typeclass interfaces but that is a bit of an antipattern.
- LockAndLol 6y agoI'm sure it has its problem domains, but does FP work well when interfacing with non-FP languages? Is that where monads / imperative language wrappers come in?
- ratww 6y agoI did a lot of stuff with F# and C# together. Calling FP from imperative is nothing special. Calling imperative from FP only requires external purity in the imperative functions (if you want FP to remain pure, of course). Not different from using pure functions in an imperative language. Monads... they're just a way of remaining pure (meaning immutable) but still doing imperative-ish stuff. You don't need monads to make software: both pure-FP and imperative languages can be Turing-complete, so you can do it all. For doing IO you can just use an imperative shell [1], that "calls" the main function of your pure-FP program (aka: a runtime). Monads for IO make it easier to communicate with this "imperative shell" while still remaining pure. It communicates with it by requiring you to return an IO object all the way to your main function. But Monads can also be used for other things that don't need IO, like "internal mutable state", "maybe monad" or even for simulating an exception system. Those don't require you returning anything. Monads are nothing special for real... but you have to use to understand them, since most explanations just make it harder to understand, so I won't attempt one. [1] https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell https://www.destroyallsoftware.com/screencasts/catalog/funct...
- rtp 6y agoI like that in some fp it is: if it compiles, it runs well (aside from pure logic bugs).
- joshsyn 6y agoI prefer functional first languages that allows mutability where needed. Ocaml, F# and scala for me. You have to go through great lengths to avoid mutability in Haskell, which is why they are developing linear types. I tried it for a few months was like nope.
- deleted 6y ago[deleted]
- ncmncm 6y agoI have looked around and determined that the only Haskell program I have ever used is Pandoc. To first order, Haskell users self-select as uninterested in writing useful programs. Anybody who would like Haskell to be seen as a language that is useful for writing useful programs would better publish programs in Haskell that do useful things than explain what they like about monads. I can confidently advise against choosing to add to the list of IRC clients, window managers or greps, or stuff only Haskell coders use. A program that does something no other program does, as pandoc does, that is badly needed, as pandoc was, would be the best choice. A second course would be to find ways to make Haskell code run fast without need to jump through hoops that corrupt the design of the program. (It might be that it is too late for Haskell to move in this direction.) There appears to be room in the world for slow languages (Python, PHP), so slowness is not deadly, but it does limit a language's appeal. Haskell doesn't need that. Prima facie, Haskell should not need to be slow. A Haskell program that generates a program in (e.g.) LLVM IR to do a useful thing quickly would serve in lieu of an actually fast program. FFTW is a fast C library for evaluating FFTs generated by an OCaml program that does not need to be very fast. FFTW relies for its speed on a cache-oblivious array traversal order (re-)invented[0] by Todd Veldhuizen for use in a C++ template library Blitz++ that, in effect, generates programs that perform matrix operations. In Veldhuizen's library, the program was in (pretty slow) C++ template meta-language that ran during compilation of the user's program, but by generating C++ in place it produced a fast program in one step. A Haskell or descendant not dependent on GC could find use in places where present Haskell is not welcome today. [0] Cache-oblivious traversal was, apparently, originally invented in the '70s to speed database operations where the the storage cached in main memory ("core") was on disk and tapes, and was then mostly forgotten. It may be re-invented again, in another generation.
- izackp 6y agoI see little difference in the core concept: pos = pos.moveBy(x,y) vs pos = moveBy(pos, x, y)