Why functional programming?
48 points| AnimalMuppet | 12 years ago | reply
I'm not trying to be a troll. I'm trying to understand. And I don't.
I've been a professional programmer for 28 years. I don't care about "the one right way" or "ideological purity" or anything like that. I care about efficiently building programs that work. (Answers that say that functional programming is in fact the one right way will be dismissed as ideologues or trolls.)
I don't think that functional programming can be the one right way, because I have spent most of my career in embedded systems. Those programs tend to be full of states that changed many function calls ago, but are still relevant. If I understand correctly, that doesn't play well with functional programming.
But once you don't buy that functional programming is the one right way, then you have to ask, "When is it the right tool for the job? And when is it not?"
(And by the way, once you don't' think that functional programming is the one right way, then things like tail recursion and the Y combinator stop looking like elegant solutions to important problems. They start looking like crocks that you were forced into because you're using the wrong tool for the job.)
Look, I get that all of computer programming is mathematics. It's all Turing machines, too, but that doesn't mean that I want to write programs like I'm writing on a Turing machine. I don't want to write them like I'm writing an abstract algebra paper, either - unless there's a real benefit.
Again, I recognize that this sounds like a troll, but I'm really not. I can be convinced. Somebody say something convincing (hopefully in terms that a professional programmer with very little abstract algebra can understand).
[+] [-] jerf|12 years ago|reply
One bit of advice though; "trying it out" means getting to the point where you can actually "do things" with it, not running through two tutorials, filtering a couple of lists and writing a factorial function. That's the equivalent of learning how to write an if statement or a for loop or something, not the payoff. It's likely been a long time since you've actually felt like you don't know what you're doing, but that will be a sign you're on the right track.
You might also be interested in this: http://www.jerf.org/iri/post/2908 Being in the embedded world, you've probably just gotten used to certain things being "just the way it is" after 28 years, but one of the things that FP can show you is that those things aren't "just the way it is"; they are that way as a result of the choices we've collectively made. You've probably independently partially rediscovered a lot of FP's insights about state management over the years; you've probably also not independently pushed it as far as the FP community as a whole has. We don't actually have to live in a world where a flag flipped 25 million instructions ago is now causing a crash in seemingly unrelated code; we chose that world. It is not without its advantages, but it is not all gravy either.
[+] [-] AnimalMuppet|12 years ago|reply
And even in the embedded world, "constrain the state space like your life depends on it" is still very sound advice, even if you can't go fully functional.
[+] [-] seiji|12 years ago|reply
In short: Languages less powerful than Blub are obviously less powerful, because they're missing some feature he's used to. But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well. Blub is good enough for him, because he thinks in Blub.
Interestingly enough, I think the Blub Paradox greatly informs how pg views people too. You can be a "normal person" or you can be a "super smart person." Normal people can't understand super smart people because it's just beyond them. (part of the whole "people aren't created equal—some are just better than everybody else" selectivist philosophy.)
[+] [-] ColinWright|12 years ago|reply
Longer answer ...
There are several issues here, and many people conflate them. They are realted, and I'm not going to be able to do them justice in a quick reply here. It's Christmas Eve, and I have a lot to do.
So, briefly ...
Learning FP, really learning it, gives you a new insight into programming. It can change the way you work, and the way you think. If nothing else, it's mind expanding, and that's always a good thing.
There's a seminal paper "Why Functional Programming Matters"[0] (WFPM). It dates back to 1984, but it's still relevant. In fact it's so relevant that raganwald[1] wrote "Why Why Functional Programming Matters Matters." That's also a really good read, with many useful insights.
fogus[2] has included WFPM in his list of 10 technical papers every programmer should read twice.[3] There's also a StackOverflow discussion[4], probably several.
There are many, many programmers out there, capable, productive, useful programmers getting stuff done, but knowing nothing (significant) about FP. And that's fine. But personally, I think that if you want to be serious about your craft, you should learn about FP, what it is, what it's good for, how to think in it, when to use it, and when to avoid it. It's not the One True Style(tm), but it is
[0] http://www.cse.chalmers.se/~rjmh/Papers/whyfp.html
[1] https://news.ycombinator.com/submitted?id=raganwald
[2] https://news.ycombinator.com/submitted?id=fogus
[3] http://blog.fogus.me/2011/09/08/10-technical-papers-every-pr...
[4] http://stackoverflow.com/questions/36504/why-functional-lang...
[+] [-] tmoertel|12 years ago|reply
1. It's different, especially from what it seems you're already doing. Therefore it is likely that if you do FP in earnest, you will learn new problem-solving techniques, many of which will prove useful elsewhere.
2. In FP, the abstraction and composition model – functions – scales down farther than do objects, modules, or compilation units. That is, functions remain practical in smaller places where objects or modules would be too expensive to insert, syntactically or semantically. In FP languages, then, you can use abstraction and composition even within the "leaves" of your logic, and that's where the bulk of your code actually lives.
3. Functions have a mathematical basis that you can draw on for intuition. In some FP languages, the mathematical parallels are so reliable that you can even calculate the code you need. Calculation can work in places where main force of intellect and the search for "aha! moments" fail.
There you go, three reasons: it's different, it not only scales up but down, and it comes with useful theory.
[+] [-] lightblade|12 years ago|reply
FP and OOP is not in conflict with each other and you can certainly use both. FP is more in conflict with procedural programming. With procedural programming you are teaching the computer how to do things step by step (for i while i < len i++). With FP, you declare what you want to be done, and the computer figure it out on its own on how to get it done. An example would be for each loop on a collection. You are not telling the computer to increment index. You just want to iterate each element. The computer is free to spawn 20 threads to iterate the collection, so long that it accomplish the goal of your expression.
[+] [-] marcosdumay|12 years ago|reply
Functional programming is too generical a label to make it possible to answer your question. It may apply to anything from C-like functions to no side effect languages, but I guess most people around here use the term to mean Lisp.
If that's what you meant, Lisp'll give you a huge amount of reflexivity that you can use to change the language so it better fits your problem. In addition, Lisp has a very simple environment, what makes it the go-to language when you must embbeb an interpreter.
Now, if you were thinking about Haskell, it has a very powerfull type system that helps creating bug-fre software, and makes type-driven programming possible. It also has explicit side effects, what makes it possible for the compiler to paralelize your program for you, and a quite good optimizer (but not as good as you'll get by manually writting low level code).
And there are plenty of other languages people call functional. Each of them have some interesting aspects that may be usefull for solving a problem. Again, it's a very generic label, and a quite old one, so that all current languages already adopt functional features - in consequence it's not a very usefull label.
[+] [-] andrewflnr|12 years ago|reply
For the OP, let me see if this connects: In C/C++ and others, we have the ternary operator, which lets you write conditional expressions. status = n > 9000 ? "yes" : "no"; This is pretty convenient, but annoying to read afterwards, especially if you start nesting. Functional languages let you have the same convenience without the annoyance. You can do things like this OCaml: status := if n > 9000 then"yes" else "no" Yes, OCaml lets you have mutable state if you need it.
FWIW, the "just functional" language in my head is a lot closer to OCaml than Lisp. Haskell and Lisp both add a lot of "extra" stuff that's cool but (obviously) not needed to be "functional". Not that OCaml doesn't have "extra stuff", but it's not as dominant. YMMV.
[+] [-] klrr|12 years ago|reply
[+] [-] MichaelGG|12 years ago|reply
The reduction in verbosity due to easier ways to approach things is what sold FP to me. Common patterns that I'd write several times in OO are easy to abstract in FP.
Tim Sweeny, of Epic Games (Unreal Engine) says that in their C++ codebase, 90% of loops are functional folds or maps.
I imagine the biggest issue, especially as an embedded programmer, is the lack of excellent optimizing compilers that let you write functional, but still obtain the same performance.
[+] [-] someguyperson|12 years ago|reply
You may look at this example and think it's contrived, that "real world problems" aren't anything like counting ways to make change. But just because you haven't encountered it in your daily work doesn't mean that other people haven't, or that it's not valuable as a tool in itself. And ultimately, having conceptual tools for thinking about problems is always going to be a good thing.
Btw, I find it slightly amusing that you don't want "to write programs like I'm writing on a Turing machine" since imperative programming maps pretty directly onto TMs as a model of computation...but I digress :)
[+] [-] RyanZAG|12 years ago|reply
So in general, forced immutable state means that any class of problems which require both high performance and a lot of mutation are going to be very poor candidates for functional programming. This is actually a large share of embedded programming and why the OP probably hasn't found that much use for it - embedded programming often comes down to accepting large amounts of data from sensors and manipulating it.
The problems where functional programming shine are where you have a chunk of data and need to create an answer from it. Given 10mb of data, should I do X or Y? Functional programming is a very good tool here because of aspects such as composability allowing for extra steps to be chained into a calculation, and immutable data ensuring that multiple steps can be run on the same data at the same time.
Hopefully that helps a bit in deciding when functional would be useful or less useful.
[+] [-] runT1ME|12 years ago|reply
You'd only have 20mb of 'numbers' if you needed to keep a reference to the original data structure. Otherwise its' garbage.
>So in general, forced immutable state means that any class of problems which require both high performance and a lot of mutation are going to be very poor candidates for functional programming
FP does not necessitate 'immutable state', even in pure languages like Haskell. What it enforces is purity, or more concrete, referential transparency. Often the easiest way to obtain referential transparency is immutable data (non destructive data structures, etc), but you can have mutable data and maintain referential transparency for high performance (see ST Monad).
[+] [-] MichaelGG|12 years ago|reply
Not all compilers perform such optimizations, but there's no fundamental conflict.
I believe Haskell's GHC compiler has "fusion" which allows it to wrap chains of maps/filters/folds into a single tight loop. That way you keep functional style, but get performance of writing it imperatively.
[+] [-] wesnerm2|12 years ago|reply
[+] [-] wonderzombie|12 years ago|reply
This isn't that different from an imperative world. If someone somewhere has a reference to some part of that data (or may have a reference), what are you going to do? Presumably you'll make a copy. In an immutable world, adding references to an existing of data is trivial. So either you accrete more data, in which case you're in the same place as the imperative world, or it's not new data, no one is using it, and you can throw it out.
In practice it's probably more complex than that, and depends on the language implementation.
There's also something to be said for something which, implementation-wise, mutates in place when possible but semantically acts as if it is immutable.
I'm not advocating for FP in embedded systems, just trying to flesh out more detail about the trade-off here. I think even if you don't program with a language with immutability, it may be worth asking questions (which may have a simple answer in the affirmative!) about the extent to which you need mutability in a given situation vs not. "Don't optimize yet" and all that.
[+] [-] yetanotherphd|12 years ago|reply
Mutable state has two uses. First, when what you are modelling really has state the varies with time (e.g. real world stuff, UI stuff). Second, as a kind of optimization.
One good reason for learning FP is that you think more clearly about what mutable state you are using and why.
For example, OOP programmers tend to instinctively write
when it could be done as They instinctively cache G(x) in case they need it later, even though this never actually happens.[+] [-] marcosdumay|12 years ago|reply
Immutable state also means that you don't need to add 1 to the numbers you won't use, and that you can discard the old numbers if you only wanted the incremented ones.
Garbage colletors and run-time optimizers are added complexity, and may be too much for an embbebed system (or maybe not, controlers are getting bigger each day). But that's a context dependent conclusion, not a certainty.
[+] [-] jetru|12 years ago|reply
[+] [-] S4M|12 years ago|reply
[+] [-] AnimalMuppet|12 years ago|reply
[+] [-] unknown|12 years ago|reply
[deleted]
[+] [-] agumonkey|12 years ago|reply
[+] [-] acconrad|12 years ago|reply
But with functional programming, I want to learn, but I have far less direction. Scala is perhaps the biggest, but still small. Haskell seems to be the most "hardcore", which appeals to me, whereas I keep anecdotally seeing people who like Ruby and Javascript (my two main languages) also tend to gravitate towards Clojure, and I can't explain why.
So far I've hated Scala's syntax and formatting (much like I hated Objective-C). Haskell is making my brain melt. Clojure seems quite simple and reminds me of my days writing Scheme in school. Any thoughts?
[+] [-] frowaway001|12 years ago|reply
Tooling and IDE support is better than Haskell and Clojure, so you can focus on the language instead of fighting with tools.
If you know Scala a bit, it's easy to move to something else. For instance, if you decide that you don't need types, you can move to Clojure; if you decide you don't need unchecked side-effects you can move to Haskell; if you decide that you don't need functional programming, you can move to a different OOP language (or keep using Scala).
[+] [-] ascendantlogic|12 years ago|reply
1) functions as first class citizens (this is a great read that relates to this: http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...)
2) immutable data: mutable state is the enemy of threading/concurrency/parallelism. Things can change underneath you in unexpected ways causing unforeseen consquences. When the data never changes, this sort of thing becomes much easier.
3) Referential transparency: This is related to #2. Whenever you pass A into function Z, you get B. There's no chance of side effects or other possible outcomes. A goes in, B comes out.
[+] [-] capcah|12 years ago|reply
One of the perks of FP is that it exposes pure functions, functions that have a fixed output for a given input. Since it's really bad at storing and mutating state(canonically can only be done via monads), it encourages you to separate your code into pure and mutable states. Let's take, for instance, a function that reads a file and xor's that with a random byte stream. A non functional approach would be something like this:
A possible way to write the same function using FP, using currying[1] and map[2](a fundamental construct in FP), one can write the code as this: While, in a naive implementation, this would be much slower than the procedural implementation, there are much stronger assumptions one can make in respect to the functions while optimizing the compiler. First, the function xor is a pure function with 2^9 possible input values, and can be substituted by a precomputed lookup table, speeding up the map function. Since the function is guaranteed not to hold state, one can also unroll the map loops, or paralelize it if needed.But remember that most "pure" FP languages are bad at keeping state, turning a huge game(for instance) into a challenge both to the programmer and to the compiler.
[1] - http://en.wikipedia.org/wiki/Currying
[2] - http://en.wikipedia.org/wiki/Map_%28higher-order_function%29
EDIT: Formmating, not used to posting to hackernews comments
[+] [-] camus2|12 years ago|reply
You dont have too,but if you like your job, you should be curious about stuff you dont know. Dont you want to learn how you could solve problems a better way?
[+] [-] dllthomas|12 years ago|reply
[+] [-] slashnull|12 years ago|reply
FP tries to do that but with behavior, so that any given operation can be cleanly plugged into other behavior via function composition and higher-levels such as maps, folds and their math-ey equivalents functors and monads. FP is pipes. Stuff goes in, other stuff comes out, and we have an elaborate system of pipe ends to make sur one's out fits into the other's in.
OOP is good when a lot of persistent state has to hang around; objects in a game, objects floating around in a GUI window, client, transaction, product objects in a commercial application that must be created, viewed, modified, deleted. Boxes in a warehouse, bills and client data in boxes, running and shooting boxes in your Xbox.
FP is good when data has to come in, be transformed, and get out. Text treatment for example. grep is obviously made out of super-low-level procedural C, but it would be a perfect fit for the FP model: a bunch of text comes in, gets chopped into lines, a search algo is performed on each one, and it all gets spitted out, possibly into another, well, pipe. Compilers are also a nice fit: yet some more text comes in, it's chopped into a flat list of tokens, it's parsed into an abstract syntax tree, then into a type-checker, then a few other pieces of optimisation piping, and finally gets spit out as some other lower-level representation.
Server-side scripting is the most common real-life application of functional-ish langs; a request comes in, a few DB requests are made, the return values are chopped into HTML (or whatever mid-level representation then html), and that stuff is spit on the wire, which as far as it goes is isomorphic to a pipe.
Symbolic computing is a pretty sweet match for FP: some notation for an equation comes in, is turned into an expression tree, then into some standard form, then gets solved or integrated or whatever (recursive tree-walks all over the place), then turned back into a human-friendly form and spit back to the display.
Pandoc is a neat Haskell lib that takes some document format (LaTeX, HTML, markdown, whatever), turns it into a unified representation, then spits it back as another format.
JavaScript is funny because it totally embraces the fact that behavior is data, that pipes are also boxes. You give a callback to a handler, which then puts stuff like it as if were a pipe, and then you have functions that implicitly act on a global, implicit data structure (the DOM), some of them that take a description of where does one want to go in the DOM and returns a node of that recursively-defined tree, such that functions can still be chained and it's relevant to do so even if they're mostly used for their side-effects. JS is the weirdest and most wonderful thing. It even has JSON, which is the funniest example of the code-is-data ethos since LISP and it's exposed AST.
But yeah, it's in the nature of FP to not care about state, so it's normal that keeping state around in FP-styled programs is a pain. Passing a global data structure/container to a function that reaches down into the structure and returns a new, modified instance of that global and re-assigning the new into the old is just... Yuck. If a player gets killed in my game state, I want to just delete it from my player list, not replace my player list with a new one.
It's in the nature of OOP to see behavior as a second-class citizen to state. In patterns parlance, FP means using the iterator and the strategy pattern all over the place as they were themselves objects. Just like OOP lets me put two objects together and have a new one, and perhaps put an array of those inside another object. In FP, I can put two different strategies together, and have another strategy, and put that compound in the iterator pattern/strategy, and have another strategy that I can still compound. Recursion (and the oldest FP performance tweak for it, the tco) is a nice way to express iteration without the use of a dedicated iteration construct, which is totally not a nice piece of pipe; you can do function composition by nesting calls [ much(foo(grunk(one_cookie_yum))); ], but it gets weird [ much(foo(grunk_many_cookies(many_cookies_yum))); but you can't do something like {for one_cookie : many_cookies much(foo(grunk(one_cookie)))}; or you could but it would be a built-in construct which you wouldn't have control over, while in haskell you can just do map (much . foo . grunk) many_cookies ]
So yeah.
If you can picture your program like a pipe, it's probably a good fit for FP. The kind of embedded softare you do probably is not remotely looking like a pipe, so you can keep on not understanding FP. If it can provide you some comfort and perhaps a laugh, the legendary pure-functional no-side-fx-allowed the-ghc-actually-is-the-flagsip-project Haskell has a runtime made out of C.
[+] [-] AnimalMuppet|12 years ago|reply
"If you can picture your program like a pipe..." may well be the answer I was looking for. Thanks!
[+] [-] L8D|12 years ago|reply
Congrats.
[+] [-] wturner|12 years ago|reply
[+] [-] NDizzle|12 years ago|reply
Why? Because I have the same questions you do, only I'm a much worse programmer/developer.
Insert Nike slogan here.
What's the worst thing that can happen by learning something?
[+] [-] AnimalMuppet|12 years ago|reply
If nothing else, opportunity cost. I can't use the same time to learn something else.
[+] [-] amirouche|12 years ago|reply
At least for the sake of thinking in another paradigm, having another «environment» to think about same issues.
I have scarce experience in functional language but AFAIK there are not only good for in->process->out like it's explained in other post.
[+] [-] bjourne|12 years ago|reply