36
$\begingroup$

I've been trying to learn the differences between Functional Programming and OOP, but I can't seem to find a comparison of the two.

A Google search did not yield any chart or table showing such a comparison.

From the unclear Google search, however, I have come up with this:
OOP keeps actions and data encapsulated in objects, Functional Programming separates the two.

But that doesn't actually give a useful comparison of the usecases for each one.

So, my question is where might I find a thorough comparison between the two, which can explain which should be used for some kinds of tasks\projects? I'm looking for some explanation\comparison of various aspects of these paradigms.

The types of projects or tasks I am referring to are varied, but can be classified into: Computer Management (similar to what userscripts allow in browsers) and Utilities (units conversion, custom shell).

(off site resources are considered an answer, as well as answers giving such a comparison without an off site resource)

$\endgroup$
  • 2
    $\begingroup$ I think this topic is already reasonably well-covered on other SE sites: I'd start by reading the first few answers in this post and this post, as well as recursively reading every linked off-site resource + every related question in the sidebar. $\endgroup$ – Michael0x2a Aug 9 '17 at 8:59
  • 1
    $\begingroup$ @Michael0x2a yikes. Well, I better get started. $\endgroup$ – ItamarG3 Aug 9 '17 at 9:14
  • 2
    $\begingroup$ In FP, functions may be applied to other functions, or to data containing functions, so the worlds of data and functions are not that much separated (think about a sort() function, with a comparator function as parameter). $\endgroup$ – Michel Billaud Aug 9 '17 at 15:20
  • 2
    $\begingroup$ I think it's also important to note how difficult it can be to read and comprehend the benefits of something like functional programming. For me anyway, it took programming imperatively for a long time to realize the complexity that modified state brings. Once I realized that was a real problem I had to solve, functional programming was a natural solution and all the material explaining what functional programming was started to make sense. $\endgroup$ – aaaaaa Aug 10 '17 at 17:30
  • 1
    $\begingroup$ I also recommend Eric Lippert's excellent answer to Why hasn't functional programming taken over yet? from SO. $\endgroup$ – Jeffrey Bosboom Aug 11 '17 at 22:02

11 Answers 11

57
$\begingroup$

There are beautiful answers to this question already here, and I will not try to reiterate any of the ground that has already been covered. However, something important that I have not seen here so far is that comparing OOP to FP is not actually terribly meaningful. It's a bit like comparing glass (the material) to tables (the furniture). They each have properties that can be explored, but even if discussions of these properties can help you learn about them, the comparison itself doesn't actually tell you much about either. Glass tables exist, and have the properties of both. Similarly, Scala, Clojure, and Swift are languages that are both functional and OO.

A more meaningful comparison for functional programming is imperative programming. The most popular languages in the world right now are all imperative1. Imperative languages focus on mutable values and states, and are historically based on Turing Machines. Functional languages are all about function calls and immutable constants, and are historically based on Church's $\lambda$-Calculus.

The two mathematical systems were developed around the same time in order to solve the same question: what is fundamentally computable? The Church-Turing Thesis, which was published before electronic computers existed, proved that the two systems were equally powerful, and further hypothesized that they could both compute any computable function, and that any sufficiently complex system could embody exactly this same computability power.

A more meaningful comparison for Object-Oriented programming would be... well, nothing. The Object-Oriented philosophy, which is about grouping data with functions that govern that data, can be applied to any programming paradigm. Object-Oriented programming is all about the scope and membership of functions and data. It doesn't matter if the data is mutable, if the order of operations is defined, how data is stored, whether functions are mutable... if we are creating instances of data that are bundled with their own functions, the we have the kernel of object-oriented programming.

1 - Only if you discount Excel, which is actually a light functional programming language, and is almost certainly used by many more people than any other programming language on earth.

$\endgroup$
  • 1
    $\begingroup$ +1, You could compare object oriented to data oriented, though. $\endgroup$ – Daniel Jour Aug 9 '17 at 19:10
  • 3
    $\begingroup$ The opposite of OOP would seem to be maintaining a separation between data and behavior. (These opposites aren't mutually exclusive, though - I've seen programs that mix imperative and functional and OOP and separation of data and behavior - they just do different things in different places.) $\endgroup$ – Brilliand Aug 9 '17 at 19:11
  • 1
    $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$ – thesecretmaster Aug 12 '17 at 23:26
16
$\begingroup$

Here I will discuss Functional Programming (FP) and Object Oriented Programming (OOP) in a fairly pure form. Actual languages, however often make compromises to allow older forms as well as multi-paradigm programming.

Both FP and OOP rely in the notion of program "State" but do so in different ways. In fact there are really two different things that go by the term "state" and it is useful to distinguish between them. The version most people focus on when they say "state" is the current set of values of variables in a program, whether held in objects or not. This depends on having modifiable "variables" in the program.

Using Pascal notation, x := 2 gives an integer variable a value 2. You set a state. Later, within the same scope, you are allowed to say x := 3 which "changes the state".

The way most people visualize the above is that the memory of some real or abstract machine has been altered in a specific way. I will refer to this notion of "state" as Explicit State. The Explicit State is the current collection of values of variables.

However, there is a different, independent view of "state" that most programmers are aware of, but don't always focus on in the same way. In this view, the "state of a computation" is represented by the program counter, which represents the current focus of execution in a program. For example now using Java-like syntax:

if(x == 3){
    // a
    ....
} else {
    //b
    ....
}

At some point in the execution the program counter (PC) may be at point (a) or at point (b), but not both simultaneously, of course. If the PC is at point (a) then the "state of the computation" includes the fact that x is 3. If it is at point (b) then the state is that x is not 3. In this view the state of the computation changes as the program executes (and is dynamic). In the former view the program state is often thought of as static, though that isn't quite correct (since things change). I will refer to this second notion of "state" as Implicit State. It is the current location of the PC and all that implies about what has gone before, no matter how implemented.

Now to the question: What is the fundamental difference between (pure)FP and (pure)OOP?

  • Functional Programming

Pure FP relies on implicit state and deemphasizes (or eliminates) explicit state. In Scheme, for example, a let gives a value to an identifier that is valid throughout a scope. It is called a variable in the documentation, but it is really a name given to a constant. Once given a value the name doesn't refer to any other value in that scope, but might have a different value in a different (or larger, or smaller) scope.

One envisions a computation in a pure FP as an expression that has a value. In order to calculate that expression other expressions are evaluated (recursively, so to speak) until some expressions evaluate to themselves (5, and "foo"). The state of the computation is purely implicit, the location of the PC within the chain of recursion. When the chain ends the original expression can yield a value.

There are no "variables" in the C or Pascal sense. Named values (i.e. constants) may be assigned to memory locations or stored on the stack, or in the cloud, or ... It isn't necessarily specified. In a sense, the state is a Stack.

  • Object-Oriented Programming

The first OOP was actually Simula (1967), developed in Scandinavia to do simulation programming. Simula was the main inspiration for C++. The first (and maybe only) pure OOP was/is Smalltalk. It became the inspiration for most other modern OOPs; Apple Object Pascal, Java, Python, .... However, most other OOP languages than Smalltalk compromise in a number of ways. To discuss pure OOP more deeply requires an excursion. There are two ways to think about an object in an OOP; it's creation and its use.

  • Object Usage in Pure OOP

In pure OOP an object exists (somehow) and is best viewed as a "bundle of behavior". It responds to a set of messages that are valid for its type. The messages may contain parameters. Whether or not it has anything like "state" is immaterial. It "behaves" when sent a message. As part of carrying out its task (as defined by the message and parameters) it may, itself, send messages to other objects. Some messages are functional in nature and return values (objects or primitive values). Some are not. You need non-functional methods to, for example, drive printers and other external devices.

In this view it is not fruitful to think of objects as encapsulating "state". The state is not material. Only the behavior matters. The implementation of an object might rely on saved state, or it might rely on delegating things to other objects through a chain of messages (again, like recursion to some base case).

Objects when used are bundles of behavior, nothing more.

  • Object Creation in Pure OOP

OOP languages, however also provide a mechanism for the creation of objects. There are two main mechanisms for this (Classes and Prototypes), which don't affect the discussion here. I will assume a class-based languages since most (non javascript) programmers are most familiar with classes. In creating a class definition the job of the programmer is to define the behavior of its objects. In Java, that means the visible methods (visibility is a bit complex for this discussion, think non-private for a simplification).

An object of the new class must be able to respond to a given message (with parameters, perhaps). That can be made to happen in different ways. For example, the object might defer the action to another object. But it might also define a (classic) variable, and give it a value. Having given that variable a value, it is free to retrieve it later and (perhaps) modify it. But don't be naive about things. Just because the name of a method is "setDisplay" doesn't necessarily mean that there is some variable (anywhere) that will have its value changed. It is a concept, nothing more. The object will somehow (up to the programmer) retain the value given so that the "state of the computation" can move forward.

So, in this "pure"OOP view, the job of creation of objects is the job of defining the behaviors to which the object can respond. You can think of an object (if you like) as a bundle of state + behavior, but that should only work at the definitional stage, not the usage stage.

Note that in this view, a computation is seen as a collection of independent objects that communicate via message passing. The objects may be normally active (actor model) or not, with the latter being more common. An object is, in the non-actor case, only active when it has been sent a message. It is easy to visualize that a PC comes along with any message allowing the object to execute the method. If the object sends a message itself, the PC is passed along as well, letting the receiver of the message act.

  • Why this OOP view is important.

One of the problems of learning to program is the problem of how much information needs to be kept in mind as you proceed. If it was necessary to retain every detail of the program, it would be an impossible task. Therefore we use various abstraction facilities (mental facilities) to reduce the task. I can't remember the state of 4gb of values. I can't even really remember the implicit state of an if-statement if nested more than about 4 levels deep. Therefore I don't write such programs and instead create simple objects with simple behaviors. I also create objects that have any explicit state private - no exceptions. I design objects so that I can effectively think of them as nothing but bundles of behavior, though Java certainly has facilities that let me do otherwise. However, if I break my rules, my program becomes harder to understand, use, and modify.

  • A pure OOP program is a set of simple objects with simple methods. The complexity of the program is in the interactions of objects, not implementations of the objects.

Yes, I write a lot of classes. But each is simple to conceptualize and simple to build. Each object is a bundle of behavior, nothing more.

$\endgroup$
  • 2
    $\begingroup$ Actually, I don't have a preference for FP. In fact the only functional language I was ever any good at was ML. I depend on concrete syntax to help organize my thoughts. My view is truly OOP, just a deeper, purer version than most people have and few OOP languages require. The only really serious software I've built (quite a bit) has been in OO languages. But my experience goes back a long way and I've tried to develop a style that is sustainable. My view isn't unique, of course. Glad I was helpful. $\endgroup$ – Buffy Aug 9 '17 at 14:27
  • 1
    $\begingroup$ Well, my "serious" software is also fun. It should be fun. In the above reference, however, serious also meant more than 15K deployments. $\endgroup$ – Buffy Aug 9 '17 at 16:46
  • 2
    $\begingroup$ Functional languages often use the term "variable" like mathematics – a name for some value which is constant during its lifetime, but the same name may refer to a different value the next time the algorithm is run. A true constant has the same value each time. $\endgroup$ – Paŭlo Ebermann Aug 10 '17 at 21:10
9
$\begingroup$

FP and OOP are both tools in the box, none of them is better or worse. The same way you would not ask whether to use a hammer or a screwdriver to put in a nail, you should not ask whether to use FP or OOP.

The question should be: what is the best way to solve your problem at hand?

OOP excels when you need to describe abstract objects with code (hence the name). Describing the abilities and features of a car is something FP is not made for, so choosing FP for this situation would be painfully complicated.

Vice versa FP excels when algorithms can be written as mathematical functions in the mindset of f(x) -> y (more complex functions are also very much possible). Using OOP to describe the result of square(x) is possible but pointlessly complicated.

In modern programming FP is usually used when volumes of data need to be transformed. If you need to map from Z to Y by using the transformation function f, then you can very simply write that in code using FP, i.E. in Java like this:

// equivalent of Yn = f(Zn)
List<Y> myYs = myZs.stream()      // for each z in Z
  .map(MyClass::f)                // transform y = f(z)
  .collect(Collectors.toList());  // and store y in a list

Doing the same in OOP would look something like this:

class Z {
  private int myValue;

  public int getValue() {
    return this.myValue;
  }
}

class Y extends Z {
  static Y convertFromZ(final Z z) {
    // ...
  }

  @Override
  public int getValue() {
    return convertFromZ(super.getValue());
  }
}

List<Y> myYs = (List<Y>) myZs;  // <-- really dirty hack

This makes it hopefully obvious why such a comparison chart does not exist: because those two are entirely different concepts.

$\endgroup$
  • 1
    $\begingroup$ Welcome to Computer Science Educators! that's a very useful overview and explanation. Thanks! $\endgroup$ – ItamarG3 Aug 9 '17 at 13:25
  • $\begingroup$ I would ask whether to use a nail or a screw, e.g.: popularmechanics.com/home/tools/how-to/a18606/… . OO vs FP is different in that it's possible (reasonable) to do both, e.g. making x.square() return a new object or writing a method expr.calculated_type() in a compiler. $\endgroup$ – Jonathan Cast Aug 9 '17 at 20:00
9
$\begingroup$

I wouldn’t expect to find a comparison table because OOP and FP are not mutually exclusive concepts.

OOP is about encapsulating data in objects behind interfaces and using inheritance to build objects in re-usable pieces.

FP, however, is about not changing states or having side-effects.

You can have an OOP program with immutable objects. (You can clone an object, but you can’t change the state of an existing object.) You can have an OOP program where the methods have no side-effects.

You can have a FP program that encapsulates its data in objects behind interfaces and uses inheritance.

$\endgroup$
9
$\begingroup$

There are already several good answers, but I'd like to add the following.

Functional code is (at least in theory) more easily parallellized. This is important because we've about hit the limit of how fast we can run integrated circuits. (I know that's an oversimplification; I'm trying to keep it simple for the OP's audience's sake.)
So, while you can't make the individual cores on your CPU run much faster, you can add cores to the CPU.

Now let's think about a single large program. Multiple cores will only speed up such a progrm if it can be broken up into independent "chunks" that can be parcelled out to individual cores. And let's be clear about what "independent" means: no one chunk should have side effects that can affect any other chunk (and probably chunks should not depend on the output of other chunks).

This is an area where functional programming excels. By contrast, trying to ensure object consistency across multiple threads or processes can be a nightmare. Which is one reason there's a great deal of current interest in FP.

The downside is ... as Jared points out, in a game, in the FP paradigm, you'd likely maintain statelessness by having the "attack" routine return a whole new copy of the orc (or whatever enemy) under attack. But:

  1. Reconstructing the entire orc is probably a non-trivial operation -- it's almost certainly going to be more costly than adjusting the orc's hit points (changing state on an existing object).
  2. And that's a fairly simple case. It's likely that changes in the object's state will cause other changes -- for example, the orc might get an attack bonus if their hit points drop below a certain level. In FP, this would probably result in constructing and returning yet another orc object. And so on.
  3. Constructing new enemy and/or player objects is going to increase the overhead of memory management. All those discarded objects are probably going to need to be garbage collected; and with all that construction / destruction, you're probably going to get fragmentation in the memory store or heap.

In general, composability is a good thing.
But it's one thing to talk about f(g(h(x))) when f, g, and h return numeric values. When each of those functions in the composition chain can return a large and/or complex object ... this may result in unacceptable performance, even for a single-player game.

Now think about a financial application, where reconstructing a customer might involve re-reading several months' (or years') worth of transaction history.


As other answers have said, FP is very good at math-y problems. If you need to manage mutable state, imperative OOP might be a better choice. Hopefully this makes the difference a bit more concrete.

$\endgroup$
  • 1
    $\begingroup$ Welcome to Computer Science Educators! A very in depth answer, addressing many points. Thanks. $\endgroup$ – ItamarG3 Aug 9 '17 at 19:22
  • 2
    $\begingroup$ There is this "Myth of the Sufficiently Smart Compiler". In theory, a compiler could inline g and h into f, perform an escape analysis to realize that the reference cannot have escaped, and therefore modify the value in place since no observer can realize the modification. Of course, a language without GC would make this much easier (eliding escape analysis partially). $\endgroup$ – Matthieu M. Aug 10 '17 at 8:15
8
$\begingroup$

I have not come across a big comparison table like you are seeking. My short reply would be:

  1. Use Functional for things that are math-y or language-y. Use it when you are investigating computability or other more academic aspect or projects.
  2. Use Procedural when you want less experienced people (such as students) to be able to understand your code in a commonsense way.
  3. Use OOP for large projects which require storing data and changing it during interactions with users.

More than this, I cannot say.

$\endgroup$
  • $\begingroup$ oh. That's a nice way of putting it. $\endgroup$ – ItamarG3 Aug 9 '17 at 12:02
  • 1
    $\begingroup$ That would be common sense. Now the trouble is, you don't always have the choice. Try programming in C++ from C++11 onwards and you will have to wrap your head around template metaprogramming even to move a matchstick. Functional programming is a fashion that invades all kinds of fields of computer engineering, willy-nilly. $\endgroup$ – kuroi neko Aug 9 '17 at 12:03
  • $\begingroup$ @kuroineko You don't need to know anything about template metaprogramming to benefit from C++11. The most a user has to worry about with templates is knowing how to write std::vector<int> and maybe a bit about how they work (i.e. template instatiation) and when the template parameters can be inferred. If you had said lambda expressions instead, I'd be tempted to upvote, but templates are usually completely avoidable. In fact, C++11 made it more avoidable: before C++11 you'd need template meta-programming to calculate factorial at compile time, now you can just use a constexpr function. $\endgroup$ – Pharap Aug 11 '17 at 17:13
  • $\begingroup$ @Pharap Good point about lambda functions, but these are not changing fundamental paradigms (no more than equivalent techniques in JavaScript or Python). Now templates are another story. They are becoming an increasingly essential part of the language. You can't completely ignore them. You need to understand at least their error messages (ouch!), and possibly the code they generate. I wonder how many people outside a tiny elite of library developpers really master them, but they sure allow quite a few guys to write cryptic code that makes a compiler crawl and spew pages of cryptic errors. $\endgroup$ – kuroi neko Aug 11 '17 at 18:07
  • 1
    $\begingroup$ I don't recall mentioning templates in my Answer... $\endgroup$ – user737 Aug 12 '17 at 13:19
7
$\begingroup$

This is a very broad question, so I'm going to point you to a couple places and then give a really short explanation.


I think it's important to have the right mindset about paradigms: At the end of the day, they're a tool to help you organize a thought process around code. They all let you accomplish the same tasks.

In general, nearly every language or environment today is 'multi-paradigm'. Even Java (which is famously OOP because everything is required to be in a class) is written in a style that blends imperative and procedural paradigms with OOP. More recently, Java includes more and more elements that are in the functional style.

When in comes to functional vs OOP, there's one big thing that comes to mind.

Being "stateless" or having "state"

State means that you have some stored properties or information, like the name of a sprite, or a display where you output information. In a functional world, especially one that tries to be "pure", you're mostly going to be dealing with composing (or chaining) functions together. The goal is to have a function, say compute_something(an_object) that needs only the information passed into it (arguments) and nothing else - i.e. It has no "state". It will then return a new value, and not modify or mutate it's input. In the OOP world, it's common to think of an object having state, it's information about itself and how it interacts with the world. You might do something like object.compute_something(), where compute_something is a property (a function) tied to that object, and by calling it, you modify some property of that object.

One common example of this is sorting a list. In a functional paradigm, you'd probably do sorted_list = sort(some_list). While in an OOP paradigm, you'd likely do some_list.sort() and the next time you use some_list it will be sorted.

Form this there are sometimes other differences, like how difference languages treat "types" of data, but things can get muddied quickly.


To answer your original question about projects: It really is hard to say. You could conceptualize a Unit conversion app as having a bunch of objects (like one for each type of measurement system), or since it's "mathy-" you could just as easily conceptualize as a simple function with a lookup table for various conversion rates. In reality the paradigm will depend on the environment that you're working with as much as it will depend on the theoretic structure of code.

$\endgroup$
  • $\begingroup$ Interesting. I had not thought of some of the things you pointed out. Thanks $\endgroup$ – ItamarG3 Aug 9 '17 at 9:16
  • $\begingroup$ “While in an OOP paradigm, you'd likely do some_list.sort() and the next time you use some_list it will be sorted.” No. With an imperative paradigm, you'd do sort(some_list) (procedural) or some_list.sort() (OO) to change some_list. OO vs procedural is orthogonal to imperative vs functional. In functional OOP, you'd do let sorted_list = some_list.sort(). $\endgroup$ – Gilles Aug 11 '17 at 0:17
5
$\begingroup$

As I read your post, I see your question as simply this:

where might I find a thorough comparison between the two [OOP and FP], which can explain which should be used for some kinds of tasks\projects?

The best explanation I have seen is in the context of the Programming Languages MOOCs offered through Coursera modeled after this course at UW.

The class works through, in order, a statically-typed functional language (ML), a dynamically-typed functional language (Racket), and a dynamically-typed object-oriented language (Ruby). Since this is an upper-division course and students presumably are somewhat familiar with it, Java is thrown in for comparisons when relevant as a statically-typed objected-oriented language.

If you want to find a thorough comparison, build projects with each approach, spending time to learn the relevant nuances of FP and OOP along the way. There is no chart or tutorial that can substitute for this work; it simply takes time and practice. Based on my experience, the work of this course will not disappoint. There are short mini-lecture videos, excellent reading notes, and challenging homework assignments all available for free.

At the end of the course, the professor does an excellent job breaking down OOP v. functional decomposition. By the time you reach the end, you will have a clear picture as to how these approaches differ, where their relative strengths and weaknesses like, and when/how to apply each paradigm to a particular problem.

$\endgroup$
  • 1
    $\begingroup$ I disagree with you that you will learn about "how these approaches differ" - they don't differ in any meaningful way. They are orthogonal concepts from the start. It's like saying that you will learn how tennis differs from clay (of which the court is made), or how paintings differ from paper. $\endgroup$ – Ben I. Aug 9 '17 at 15:36
  • $\begingroup$ @BenI. I'm not enough of a programming languages expert to speak with my own authority on such a matter, but I have a feeling the professor sees things differently. The end of the course dives in to how each approach takes fundamentally opposite approaches to program decomposition. From the lecture notes: "We show that the two approaches [OOP and FP] largely lay out the same ideas in exactly opposite ways." courses.cs.washington.edu/courses/cse341/17sp/unit8notes.pdf $\endgroup$ – Peter Aug 9 '17 at 15:39
  • $\begingroup$ In that case, my disagreement is with the professor. Perhaps he overstated his case? The sentence that you have quoted would seem to be disproven by the existence of the many functional, object-oriented languages. $\endgroup$ – Ben I. Aug 9 '17 at 15:51
  • $\begingroup$ @nocomprende Your statement is beautiful, but I suspect it is ultimately more poetic than it is meaningful. Godel is talking about the limits of provability, which is simply unrelated to the notion of whether various engineering philosophies can play nicely with one another. $\endgroup$ – Ben I. Aug 9 '17 at 17:05
5
$\begingroup$

It is difficult to put this into a form that inexperienced undergraduate students will appreciate, but well...

"It is commonly the case with technologies that you can get the best insight about how they work by watching them fail" - Neal Stephenson, In the Beginning was the Command Line

Since OOP is the 500lb gorilla of the programming world and FP the relative upstart this comparison might be a bit skewed for want of data points. However two prominent failure cases come to mind.

1. Data Interchange

The Sun God Microsystems bestowed upon the Paladin Knights of Programmingdom his Commandments etched into tablets of purest silicon for posterity and from whence His disciples founded the Church of Java. Any who were so foolish as to claim that these commandments from on high were a Pact with the Devils of Performance and Popular Sintax bastardizing the pristine purity of Smalltalk were denounced as heretics. And the proselytizers spread their faith unto the remotest corners of the kingdom.

So yeah, people started using OOP for everything. But there was (at least) one arena where this failed entirely: IPC. Exchanging data across the boundary of threads/machines/networks/languages was an issue. Exactly how much of an issue can be inferred from enumerating some of the (many) attempts at solving it in an OO fashion that litter the scrapheap of history: CORBA, RMI, WSDL, DCOM... and also from the extreme proliferation of using plain ol data generally in the form of XML (as opposed to using XML-RPC).

So now people still do this, but generally we use transport mechanisms that are stateless (like HTTP) and over them we send language-agnostic data (sexprs, XML, JSON). So in structuring intercommunicating systems FP won because dealing with state is hard enough on its own but becomes an order of magnitude harder when you add threads, then another order of magnitude (or two) harder when you add inter-machine communcations and another order of magnitude (or more!) harder still when you have many-to-many communications with needs for eventual consistency or accurate chronological ordering of events. In fact, just thinking about it enough to write that last bit raised my blood pressure to dangerous levels.

Which is not to say that people don't use OOP in that space at all anymore, but it's not super common and the search to find the Great-OOP-Protocol-that-Makes-it-EZ is a pursuit more easily compared to cold fusion or the Lost City of El Dorado (no VC funding for you!) than trying to create the next megahit social networking app (\$\$purr\$\$).

2. Games Programming.

Programming games in FP is certainly possible, but not necessarily recommended. And this goes beyond lack of libraries and middleware or the need to squeeze as much out of the hardware as possible: when my level 6 Half-Elf Berserker bashes an orc with her Hammer of Doom I expect that orc to be the same orc minus 3D6 hp not a new orc with a different hp value than the old one. The problem is that games deal generally with two different entities: things and rules about the interaction of those things as they change over time. No one wants to go all War Games and watch a supercomputer play itself at tic-tac-toe until it learns the futility of nuclear war. OOP sucks at modelling rules (which FP excels at), but OOP is great for modelling how reified things change over time.

Anyways, as I said at the beginning I'm not exactly sure how I'd go about making all of this accessible to CS undergrads (based on my memory of what I and my peers were like when I was one). But I think looking at two high profile failures is a good place to start.

Bottom line, as the other answers have said they are extremely different mental models. Anytime you try to use one that doesn't easily map to the problem domain you're going to have issues. So illustrating some of those domains with how one or the other is a more natural fit (and my examples above of shoving square pegs into round holes) and why might be the best way to approach the topic.

$\endgroup$
  • 3
    $\begingroup$ Your history is backwards. Object Oriented programming is the newbie here. FP predates the earliest proto-OO language by a decade. LISP, a functional language based on Church's Lambda Calculus, was the second high-level programming language ever created. It came out just 2-3 months after FORTRAN (the oldest high level programming language that exists). $\endgroup$ – Ben I. Aug 9 '17 at 18:53
  • 3
    $\begingroup$ @BenI. I'm aware of the chronology. But in terms of mindshare in the lifetimes of undergraduate students (last ~20yrs) OOP has been utterly dominant. As programming and computer science are rapidly growing, a decent subset (majority?) of persons in the craft came of age in the Javassic Era and are just now being exposed to what LISP and Erlang (and ML and Haskell) have been doing for decades. $\endgroup$ – Jared Smith Aug 9 '17 at 23:38
  • $\begingroup$ Regarding your games programming argument: John Carmack taught his son game programming in Racket, a LISP dialect. $\endgroup$ – ComicSansMS Aug 10 '17 at 12:01
  • 1
    $\begingroup$ Another hurler of colorful metaphors! About time, too. $\endgroup$ – user737 Aug 10 '17 at 13:53
  • 1
    $\begingroup$ @nocomprende inexact metaphors are always stickier than exact but dry technical discourse :D $\endgroup$ – Jared Smith Aug 10 '17 at 15:07
4
$\begingroup$

Truth be told you will never truly understand the difference until you actually learn and use both a functional and an object oriented language. I say that as someone who has learnt both object-oriented languages (C#, C++, Java) and functional languages (Haskell). I will however attempt to explain the crux of it.

My answer will be looking mostly at functional programming from an object-oriented point of view, as the features of object oriented programming are already very heavily documented due to its popularity whereas the features of functional programming are much more scarcely documented.


Firstly imagine a computer language as a tool (or a collection of tools). Every tool in the world has a particular purpose or a set of tasks that it's good for, and the same is true of languages.

Functional languages tend to work better for problems that involve data-processing and deterministic processes. As such they are liked by scientists and mathematicians who can think in terms of pure data and algorithms.

Object oriented languages are better for modelling complex interacting systems. They are preferred by businesses because of their ability to mediate complex tasks and by game developers because of their usefulness in modelling the world.

They can of course be used for other things, but these are typically what the approaches are centred around.


At the language level the crux of the matter that sets functional languages apart are these factors:

  • They shun local state (with caveats).
  • By extension they forbid functions from having side-effects (functional 'purity').
  • Implicit type inference
  • The dependence on 'lists'
  • The use of sum types
  • The use of type classes (or 'contracts')
  • Implicit currying

(Note that these are not all completely necessary for something to be considered a functional language, but they are features that are commonly associated with functional languages because they can be found in a wide variety of functional languages, particularly the most popular ones.)

State:

In a functional language, values are created and they do not change - instead the old value is destroyed and a new value is created. At face value this seems a bit crazy, but in reality this abolition of state (with caveats) is only at the language-level, the compiler translates the source code into machine code that still has a concept of state, which somewhat breaks the illusion and makes this notion a bit less shocking.

The caveats I mentioned are that there are ways to emulate state in one way or another. One way of doing this is by maintaining the state as a function variable and another way is by using a construct called the 'State monad'.

Purity:

Equally, although all functions must be 'pure' (i.e. must not have side effects), there are various monads that allow there to still be side effects without breaking the laws of the language. Monads are how any kind of IO (file IO or terminal IO) is made possible. Essentially, the behaviour of the program is bound to the monad through functions that act a a means of sequencing - the monad then represents a list of actions that describe the translation and processing of the data.

Type inference:

(This is not found in all functional languages and may sometimes be found in certain object-oriented languages such as C# and C++.)

It is where the compiler is able to evaluate the type of an expression by looking at the types of the values and functions in the expression and deducing what the valid results could be. If there is only one unambiguous match, the compiler is able to infer the type, making many definitions as simple as the values and functions themselves - no explicit typing involved. In other cases, there is ambiguity and some type notation must be used to disabiguate the result.

Lists:

Furthermore, the core data type of functional languages is a kind of singly-linked list often referred to simply as a 'list'. This takes the role of the basic collection datatype and is used how arrays and dynamic arrays are used in many object-oriented languages.

Another significant difference is the language-level support for sum types. Sum types are like objects with different constructors, and work sort of like inheritance in object oriented languages. That is, the type itself can be an abstract concept (e.g. a tree) and the constructors for it can represent different values of that concept (e.g. and empty tree, a leaf node and a branch node). It is because of this that tree data types are very easily represented in functional languages, and as such they are well-liked for solving mathematical tree-related problems and for implementing parsers and/or compilers.

Type Classes (or 'contracts'):

Type classes are similar to interfaces in object-oriented programming but have the distinction that they are not implemented at the time the datatype they are applied to is defined, they are implemented after the type has been defined. This means that they can be applied after the fact to datatypes that one does not 'own'.

Type classes are defined simply as a 'contract' that list a series of functions and/or values for which the obeying datatype must have an implementation. In effect the 'contract' states "any datatype said to implement this type class must provide an implementation for these functions".

For example there could be a datatype addable a (Where a is a type variable - a symbol standing in for an as yet undeclared type) that specifies a function called add with the signature 'a -> a -> a' (a function accepting two arguments of the type represented by a and returing a value of the type represented by a). This loose definition means the addable type class can be implemented by many different data types, including Int, Float and any user-defined arithmetic type.

Currying:

(Note that not all functional languages support this, but it is much more commonly found in functional languages than in object oriented ones.)

Currying is a technique named after Haskell Curry (the same person for whom the language Haskell is named). Currying means that a function is only bound to one argument and then returns either a result or another function accepting an argument. In functional languages, all functions are implicitly curried, which means that no function actually accepts more than one argument. This in turn means that when you write a statement like add 3 5, what actually happens is that the 3 gets bound to the add function, which then returns a function accepting a number, to which the 5 gets bound, and only then does that return a result.

This probably seems insane to someone not used to the idea, but it actually makes a lot of things much easier. For example there is typically a function called map which takes a function and applies that function over the contents of a list (much like a loop could do in imperative languages), and the ability to curry functions makes the definition of a the function passed to map very simple. If, for example, you wanted to add 5 to a list in Haskell, you could simply write map (+5) list - the (+5) takes the function +, binds it to the value 5 and returns a function taking a number and returning the result of that number + 5.

$\endgroup$
  • 3
    $\begingroup$ I am afraid this answer conflates Functional Programming and Haskell. At the very least Type Inference, Type Classes and Currying are NOT a hallmark of FP. $\endgroup$ – Matthieu M. Aug 10 '17 at 8:12
  • 1
    $\begingroup$ Lisp is dynamically typed, so no type inference is necessary (or meaningful). Possibilities for making it statically typed are discussed here. $\endgroup$ – Matthieu M. Aug 10 '17 at 10:57
  • 1
    $\begingroup$ @nocomprende I am aware of that saying and its application to computer languages. It's often applied to patterns too. (The bit I got hung up on was the word 'tale', which as far as I know is a synonym for 'story', which doesn't really fit the sentence.) Technically humans are animals. At any rate, this seems like an esoteric, slightly poetic tangent rather than a comment about the answer. $\endgroup$ – Pharap Aug 10 '17 at 17:44
  • 1
    $\begingroup$ @nocomprende "I was just putting in a warning that we often need to take a step back from debates and consider carefully the basis for the whole question. " - that is a fair point. What you had to say was valid, I think your earlier comments were just a bit too crypic at what they were getting at. (Either that or they made perfect sense and I'm just tired.) It's up to you whether you delete them or not, they might be considered 'too chatty' they might not be - I can't really say either way. $\endgroup$ – Pharap Aug 10 '17 at 18:07
  • 2
    $\begingroup$ Currying is inherently possible in any functional language, but whether it's used has little relation with the design of the language. For example Standard ML and Ocaml have a very similar core, but SML favors tuples as function arguments while Ocaml favors currying. (“Favors” means that this is what the standard library uses and the compiler optimizes this case better.) $\endgroup$ – Gilles Aug 11 '17 at 0:21
3
$\begingroup$

Functional programming is all the rage nowadays. It relies on the illusion that stateless objects and immutable data structures will keep the bugs out. Which is of course nothing but a self-fulfilled prophecy.

Statelessness and immutability are the current Holy Grail and Golden Fleece, mostly because they allows to write junk code faster (a function will always return the same result regardless of the current state of the system, so once it's debugged we're pretty much safe, right?) and we don't know any better way of using these new shining CPUs (functional programming requires a lot more resources than plain old procedural / object oriented approaches - just look at the exponential growth of C++ compile time!).

Functional programming is easier and faster to write. It encourages to concentrate on generic and simple building blocks and follow a bottom-up path.
On the other hand, it tends to become verbose and unmaintenable fast, what with the layers upon layers of progressively complexified functions you have to write.

With OOP you need at least some top-down approach. A general sketch of your final architecture before you can even start coding. After a couple decades of "extreme" and "agile" programming, the dreams of an easy way out of this initial planning are slowly starting to recede (after having cost an unknown but probably sizeable fraction of the world's GDP in failed projects and botched software). Now, with the shiny GAFAs as role models, functional programming is taking over as the new magical paradigm.

$\endgroup$
  • 4
    $\begingroup$ This is full of unsubstantiated opinions and reads more like a rant than an answer. Hype is orthogonal to utility: things can be anywhere in the matrix of sexy/boring and useful/superfluous. $\endgroup$ – Jared Smith Aug 9 '17 at 17:10
  • $\begingroup$ I wouldn't say it's all the rage. The only popular 'functional' language I can think of is Rust, and that's more than just plain 'functional', it has OO characterists too - it's more of a hybrid. All the older functional languages are preferred by mathematicians and scientists (or people who like maths and science). Granted many OO languages have adopted pseudo-functional features such as lambda expressions, that doesn't necissarily make them interested in the functional paradigm, rather the implementors have seen a feature they liked and found a use for and adapted it to suit their needs. $\endgroup$ – Pharap Aug 9 '17 at 21:55
  • 1
    $\begingroup$ @Pharap indeed, that's what I call whims of fashion. Templates in C++, stateless and immutable structures all over the web, JavaScript and Python being used as poor man's FP... Immutability and statelessness can be very appealing when dealing with concurrent processes (multithreading that has become mandatory since we started hitting the speed limit of a single CPU, or networking, after 30 years of calamitous efforts to synchronize data brokers), but still they are no silver bullets: costly, hard to read, relying on increasingly complex (and unreliable) tools to reach OOP performances. $\endgroup$ – kuroi neko Aug 9 '17 at 23:30
  • 1
    $\begingroup$ @kuroineko I wouldn't knock templates because they can be incredibly useful and time-saving in the right hands (emphasis important), the ability to write them is certainly more useful for library writers than regular programmers though. I can understand the want for statelesness (it's more about the web's core principle of robustness), but I agree with JavaScript and Python creeping into territory they are unfamiliar with (functionalism). $\endgroup$ – Pharap Aug 10 '17 at 0:06
  • 1
    $\begingroup$ @JAB They can do if the problem they are solving is well suited to generics (such as writing datastructures or arithmetic types), but those cases occur less frequently than the standard case for most programmers. Plus there's the same danger with templates as there is with programming patterns, and that's the "this is cool, let's make everything a template" mentality. The danger doesn't come from the templates themselves, it comes from them being considered a cool thing to know - it attracts people who will learn how to use them but not when to use them. $\endgroup$ – Pharap Aug 10 '17 at 1:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.