71

How does a functional programming language, such as Elm, achieve "No runtime exceptions"?

Coming from an OOP background, runtime exceptions have been part of whatever framework that is based on OOP, both browser-based frameworks based on JavaScript and also Java (e.g., Google Web Toolkit, TeaVM, etc. - correct me if I'm wrong though), so learning that functional programming paradigm eliminates this is big.

Here's a screen grab from NoRedInk's presentation on Elm, showing the runtime exceptions from their previous JavaScript-based code to the new Elm codebase:

Enter image description here

  • How does the functional paradigm or programming approach eliminate runtime exceptions?
  • Are runtime exceptions a great disadvantage of OOP over functional programming?
  • If it is such a disadvantage, why have the OOP paradigm, programming approach, and frameworks been the industry standard? What is the technical reason? And what's the history behind this?
21
  • 22
    I don't know Elm: what happens in Elm if a function divides something by 0 ? – Christophe Jan 10 at 18:33
  • 20
    @Christophe: I don't know Elm either, but there are essentially three choices: a) division is not defined for 0, i.e. the / function has type (Num, NonZero) -> Num. So, you get a compile error if you cannot prove to the type checker that the second operand is guaranteed to be nonzero. b) / may return an error or None, i.e. its type is (Num, Num) -> Error Num or (Num, Num) -> Maybe Num. c) you simply define a legal result for it, e.g. a / 0 always returns 0 or +Inf or -Inf or whatever rule you want to implement. The point is: it will never fail, it always returns a value. – Jörg W Mittag Jan 10 at 19:44
  • 70
    You can trivially "eliminate" all runtime exceptions from JavaScript. Just define the runtime exception to do nothing and continue execution. Any presentation about whether one language is "better" than another which contains that sort of snake oil isn't worth wasting time on, IMO. – alephzero Jan 11 at 2:55
  • 20
    @alephzero That makes the assumption that an Elm (or whatever language) program will compile if it is possible for it to have a runtime error. Look at Rust, the most loved programming language for four years straight. It is (almost) impossible to cause a runtime error (that isn't a panic) in Rust, but it's not "snake oil"...it's just clever design that prevents code that can potentially error from compiling. – Redwolf Programs Jan 11 at 4:05
  • 43
    Having exceptions is a language design choice. All this says is a language with exceptions has more exceptions than a language without them. Shocker. – Nathan Cooper Jan 11 at 9:17

10 Answers 10

145

How does a Function Programming, such as Elm, achieve "No runtime exceptions"?

That's easy. You simply don't write functions that fail.

That might sound simplistic, but that's the gist of it.

Take division, for example. We can simply define that anything divided by 0 is 42. Boom. Now, division no longer throws a runtime exception, it just sometimes returns a wrong result.

Elm's choice are a little bit more intelligent than that, however:

 1 / 0
--=>  Inf

-1 / 0
--=> -Inf

 0 / 0
--=> NaN

Note that this is only one possibility. Another possibility would be to introduce a "nonzero number" type that is distinct from "number", and then the type of / would be

(/) : Float -> NonZeroFloat -> Float

instead of

(/) : Float -> Float -> Float

as it is now.

A third possibility would be change the return type, for example like this:

(/) : Float -> Float -> Maybe Float

This means that the function returns "maybe a float". More precisely, it will either return Just Float or Nothing.

Or, if you want some more information, the type could be

(/) : Float -> Float -> Result String Float

This will return either an Ok Float with the value wrapped into the Ok data constructor or an Err String with a description of the problem wrapped into the Err data constructor.

Another example is retrieving a value from a dictionary: what if the key does not exist? Or indexing into an array: what if the index does not exist? Well, both the get function for arrays and the get function for dicts return a Maybe. In some other languages, there is also an additional function called getOrElse which takes an additional argument, and returns that argument if the key is not found.

The key point is simply to write your functions in such a way that they never throw an exception and always return a value.

Note that this has nothing to do with Functional Programming. You can do this in any language. For example, C also has no runtime exceptions. In C, you use "magic" return values or error codes to signal errors.

You could do this in Java as well. In fact, Java ships with an implementation of that Maybe type called java.util.Optional and you can write a similar Result type as well.

Go has multiple return values, and it is customary to return an additional error code value from a function. For example, a hypothetical get function for a dictionary would not return item and then maybe return null or crash if the item cannot be found, but rather it would return item, found, where found is a boolean value telling the caller whether the item was found, and you would use it something like this:

item, found := dict.get("key")
if (found) {
  // do something with `item`
}

Coming from a OOP background, runtime exceptions have been part of whatever framework that is based on OOP, both browser based frameworks based on Javascript and also Java (e.g. GWT, TeaVM, etc), correct me if I'm wrong though, so learning that Functional Programming paradigm eliminates this is big.

It has nothing to do with Functional Programming. FP certainly helps but is not a requirement.

If you write your Java code in such a way that you never return null, never have un-initialized fields, and never throw exceptions, then you can achieve the same thing for your own code. The problem is, of course, that everybody else's code, including the Java SE standard library, still returns null and throws exceptions. So, it is as much about the standard libraries and the discipline of the community as it is about the type system and the language.

Of course, there are things the type system and the language can do to help you. For example, it can do exhaustiveness checking, i.e. it can make sure that you always check for both Ok and Err in your code. This is not possible in Java, for example. But again, this has nothing to do with Functional Programming or Object-Oriented Programming.

Haskell is actually a very good example: Haskell is a Functional Language and it does have runtime exceptions, but the community simply chooses to never use them, but use Maybe, Error, etc. types instead.

  • How the Functional Paradigm or programming approach eliminates runtime exceptions?

It doesn't. Writing code such that it never throws exceptions eliminates exceptions.

  • Are runtime exceptions a great disadvantage of OOP over Functional Programming?

They have nothing to do with OOP or FP.

  • If it is such a disadvantage, why is OOP paradigm, programming approach and frameworks have been the industry standard?

I would argue they are not the industry standard. While a lot of code is written in Java, C#, etc., the overwhelming majority of that code is not Object-Oriented but rather Structured / Procedural / Modular with Abstract Data Types.

What is the technical reason?

Most "popular" technologies are not popular for technical reasons. At no point in history were DOS and/or Windows technically superior. They just had brilliant marketing and business-savvy managers.

And what's the history behind this?

Unix becomes popular, with Unix comes C, C becomes popular even outside of Unix, C++ adds a misunderstood mangled castrated idea of OOP to C, C++ becomes popular, Java kinda-sorta looks like C++ even though it is actually much closer to Objective-C and Smalltalk, IBM goes all-in on Java, and there is the universal truth of IT: "Nobody ever got fired for buying IBM."

Is this cynical? Yes, but that doesn't make it untrue. More often than not, the people who decide whether or not to buy some technology do not have the technical expertise to judge whether the technology is actually good or not.

19
  • 16
    This is a good answer. But note that returning a wrong result can be worse than returning an exception. Java is also returning +/- infinity on floating point division by zero. I once spent a lot of time tracking down a bug caused by this behaviour which would have been obvious if there was an exception in the first place instead of a wrong result – Manziel Jan 11 at 8:49
  • 26
    @Manziel: Sure, but it seems that the OP is confused how achieving something like "no runtime exceptions" is even possible in principle, and "just return a result, any result, no matter how stupid" is one possible answer to that. It's not a good answer, but is an answer. – Jörg W Mittag Jan 11 at 8:59
  • 3
    “Haskell...community simply chooses to never use [exceptions]” – not really. Haskellers quite often use exceptions in non-critical IO code, much like they are used in Java. In pure code (which is typically 90% of the work) not so much. Though it's common to use error for “this must never happen” states, which can in fact also be caught as an exception – though that's indeed eschewed. – leftaroundabout Jan 11 at 9:28
  • 5
    Another "One True OOPer." – Robert Harvey Jan 11 at 15:32
  • 7
    Elm's choice are a little bit more intelligent than that, however: Elm isn't more intelligent than any other language that only uses IEEE754 math. If you try to do a division by 0 in Javascript, you'll get +Inf, -Inf or NaN. It is the integer division that creates problems. – xanatos Jan 11 at 18:21
18

How the Functional Paradigm or programming approach eliminates runtime exceptions?

Elm does it by encoding return values as Maybe or Result instead of causing a runtime error.

type Result error value
  = Ok value
  | Err error

Are runtime exceptions a great disadvantage of OOP over Functional Programming?

As I understand it, the goal of Elm is to make the type system smarter so that it can catch these kinds of problems at compile time. There's a clear and obvious benefit to this; catching any problem before the program ships is vastly less expensive than catching it after it has deployed.

But there are other things to consider. Functional Programming languages are quite different from Object-Oriented programming languages in a number of different ways; error handling is just one of them.

Object-oriented developers would probably tell you that languages like Java have the benefit of market share and mindshare; exception handling is merely the routine result of what happens when someone's JSON isn't properly formatted.

And managers who do not have a deep grasp of programming languages and their tradeoffs are likely to make the safe choice and go with a programming language that is well-known, well-understood and easy to find developers for.

If it is such a disadvantage, why is OOP paradigm, programming approach and frameworks have been the industry standard?

In large part, because these things have been around for quite a long time, and the vast majority of developers writing the vast majority of applications understand them. Object-orientation, for the most part, provides a huge benefit over previous approaches; it allows programs to scale up in a way that wasn't possible before. Exception handling is also an improvement over what we had before OOP came along.

Your question seems to be based on the premise that the "best" thing (for some definition of "best") will always rule the day. That's simply not the case.

8
  • 7
    "it allows programs to scale up in a way that wasn't possible before" I hear that phrase a lot but I'm not sure I've ever understood what it means. Scale up how? I can think of a lot of things that could mean. Scale to more cores. Scale to more boxes. Scale to larger teams. Scale to larger LoC. Scale to larger systems of programs. I'm never sure what people mean when they say that, or why they feel that way about OOP. – Jared Smith Jan 11 at 12:12
  • 2
    Scale to larger teams, larger LOC and larger systems of programs. – Robert Harvey Jan 11 at 15:25
  • 2
    Part of the success of OOP is probably also because it sounds so familiar to the uninitiated - everybody thinks they know what it it means to think about "objects", their properties and their "relations" - parents, siblings, children, etc. It is only after learning quite a bit about it that people realize most of this initial intuition is based on misunderstandings. FP terms, on the other hand, appear incomprehensible at first - function (ok, had math at school), monad (no idea a priori), type classes,... it just sounds less accessible to a non-math background beginner (or manager). – Hulk Jan 11 at 15:43
  • I'm not trying to be argumentative and I appreciate your response, I never personally had to work on a large procedural system back in the day. But my impression of the existing lore is that OOP isn't necessarily great for that. So I'm curious: how much of the gain in scalability was due to the simultaneous (and related) introduction of things like garbage collection and a not-as-leaky string abstraction vs the object orientation part? I feel like it's tough to have a feel for that if your career began in the era of Java. – Jared Smith Jan 11 at 15:43
  • 7
    @JaredSmith: Just to make it clear, I'm not an advocate of growing code bases unnecessarily, and agree with everything that Steve Yegge says in that article. Java is popular, not because you can write smaller code in it (you can't), but because you can put everything in your closet into tidy little boxes. Also, because everyone who knows Java (and the cadre of accompanying software patterns) can read it and understand it. But, all other things being equal, a huge pile of organized code is still better than a huge pile of disorganized code. – Robert Harvey Jan 11 at 16:36
17

It doesn't remove errors. It just uses the type system to force you to handle all errors explicitly.

To understand Maybe types, you had to go back to older languages like C that doesn't have Exception. In C, errors are usually indicated by returning status/error code that you have to remember to check every time you do an operation. The problem with this approach is that you can often forget to check the status code, and let errors pass through.

The innovation that Exception brings is that it separates business logic from error handling and it also forces the program to handle errors when using checked exception. Separating error handling from business logic makes for a very legible code, but has the drawback of adding a secondary, implicit code path when exception happens. Checked exception is rather unwieldy because it forces you to handle errors when you often don't care, so many languages do the practical thing and turns most errors as unchecked exception as a deliberate design decision.

The "Maybe" type is an alternative solution to the problem by again requiring that you check for return status/error code. The improvement that "Maybe" adds is that these languages has is that their type system to force you to handle the error while unwrapping a function's return value, you can only get to the return value if you've written the error handling construction. So there is no implicit secondary code path but the type system will tell you if you forget to take error handling into account.

Notice that the main benefit here, which is requiring programs to handle exception, is pretty much what "checked exception" is to "unchecked exception". "Maybe" is basically a "checked status code" to the "unchecked status code" that languages like C only have.

Forcing every code to handle every error makes for a very verbose code, so many functional languages provides shorthand syntax for adding common boilerplate error handling to just crash the program or to rethrow the error. The effect of this "shorthand syntax" is effectively similar to the stack trace unrolling that's automatically added to every function call in languages that has exceptions. So with languages that has a shorthand syntax for unwrapping Maybe, you basically just end up with two different function call syntax, the default one that you theoretically should be using to take advantage of the the type system to remind you to check for errors, and another function call syntax that everyone actually uses to shut up the compiler because handling error is actually quite cumbersome and crashing actually works just fine to handle most edge cases in most programs.

So we've come full circle, in that effectively the difference between languages that uses Exceptions and languages the uses Maybe is that languages that uses Maybe requires that you mark your code with these special syntax anywhere that an unchecked exception can happen in languages with exceptions, while in languages with exception, you just assume that any code can throw an error and just accept that most errors are inconsequential.

3
  • 4
    Re the error code, it's not just that you can forget to check it, but also that when you check the error code at the lowest level, you need a mechanism to pass that error code back up the call stack until you find somewhere which can deal with it appropriately. , This is somewhat inconvenient if you have to do it yourself, and as we all know, if it's inconvenient then people are likely not to do it routinely. Exceptions provide a built-in mechanism to make this convenient, which (should!) make it harder for people to make excuses about not checking it. – Graham Jan 11 at 14:25
  • This! This is the spot-on answer. Such algebraic data type systems are more common in functional languages, but they are available in procedural languages, too. Rust is a good example. If you have no recourse but to use them to express errors, you have no runtime exceptions. – lvella Jan 13 at 10:39
  • I'll note that Java went a step further in the quest to make exception-checking more routine. However, they only did this for exogenous and vexing exceptions (here, I'm using using Eric Lippert's Categorization ). Some imperative languages try to block code with uncaught exceptions through Code Contracts (i.e., they statically analyzes code for possible exceptions and requires you to either catch them yourself or make them part of the contract (caller must catch them). – Brian Jan 13 at 14:12
8

Run-time exceptions are not related to the programming paradigm chosen, but to the fact that whatever the language chosen, something can go wrong at run-time:

  • Exception handling emerged as a special control flow mechanism before OOP, and appeared in the context of structured programming in PL/1 in the 70s.

  • Functional programming languages handle exception via the data flow and many functional programming language comme with constructs (e.g. Erlang, Scala, F#) like try some-expression but-if-it-goes-wrong other-expression.

Elm innovates somehow: instead of having to explicitly rely on some try/else it returns special values to indicate that there is an exceptional situation. So it's not that there are no exceptions handling, but the handling is by defaut: exceptions are just caught behind the scene and transformed into special data values. The big advantage is that there is no longer to wonder whether to raise an exception or return an error code, a typical design dilemma in languages with exception handling.

Let's take the divide by 0: while many languages raise an exception, Elm just returns a special value (0 for the division and NaN for the modulo, if I understood well). The special value then propagates to the other expressions without never abruptly ending anything. While it is very comfortable to get these things handled by default, in the end, it is not very different from the try some-expression but-if-it-goes-wrong other-expression because you cannot really use an expression resulting from a divide-by-zero to do something useful and sooner or later you have to check if the result was valid.

1
  • Elm isn't really "innovating" here, it's following a long tradition among Functional Programming languages of representing errors and alternative scenarios in the type system. Haskell, in particular, comes to mind. – IMSoP Jan 12 at 13:59
3

The core idea of functional programming is choosing the right types for your function.

I will borrow the example of an easy and most evident example. Divide

// Note: Scala Code
// Here you cannot avoid the exception,
// because your function is tied to a primitive data type
def divide: (Float,Float) => Float = (a, b) => a / b

// Here the function makes it evident in its output type
// that there is chance of some kind of failure.
def divideHandled: (Float, Float) => Option[Float] =(a, b)=> b match {
  case 0 => None
  case _ => Some(a/b)
  }

In functional programming, you acknowledge that a function may fail so you encode it with the correct type, which forces you to write the function more precisely, to avoid runtime errors.

When correct types are chosen

  1. the function becomes more descriptive, but it still hides the implementation (until you investigate)
  2. It makes it harder to introduce bugs, as now you are constrained to the type you have chosen
3
  • 1
    I wouldn't argue this to be true for all functional languages -- there exist FP languages that play fast-and-loose with typing... quite a lot of those in the LISPy family, for that matter. – Charles Duffy Jan 11 at 17:09
  • Hmmm; floating point divide doesn't throw, but if you change it to mean Int I've got another beef with that code. – Joshua Jan 13 at 2:48
  • Just to nitpick, optional does not make it obvious if you actually chain it. If a method does not return the result you don’t know if the input was absent or the value was 0, so it’s often better type it more. And this comes with lots of handling were functional syntax for comprehension shines. – eckes Jan 13 at 10:28
1

As others have pointed out, the core difference here is in the way the effect of an exceptional situation is handled. In functional programming, it is somewhat common to encode the possible exception states into the return type. This can get a bit conflated when you start having both business errors and technical exceptions encoded into the same error types. Some newer languages have tried to address this by introducing an "Effect System" that is parallel to the type system. One such language is F*, which uses an effect system to produce code that is fully verifiable. This is much closer to the "genuinely no runtime exceptions" state that you seem to be curious about. You can check out the F* tutorial here: http://fstar-lang.org/tutorial/

0

It is not an achievement, it is a limitation.

The functional paradigm does not allow the abortion of an operation. Every operation must yield a result which you, the programmer will ultimately still have to deal with. "No exceptions" may read as "no errors" or "no problems". That is not the case, it is just a different way of dealing with errors/problems.

9
  • 8
    "The functional paradigm does not allow the abortion of an operation. Every operation must yield a result which you, the programmer will ultimately still have to deal with." – What you describe is Totality, not Functional Programming. FP, in general, does not prohibit diverging computation. (In fact, a language like you describe is not Turing-complete, while most FP languages are Turing-complete, and thus partial.) – Jörg W Mittag Jan 10 at 21:05
  • 6
    Its not a limitation, its a different way of dealing with certain things. Different, not more limited. In fact I'd consider it more powerful and more clear. – Polygnome Jan 11 at 1:51
  • 9
    @Polygnome Actually I think that OP is right here, it is a limitation. But Id argue that it is a limitation in the same way as "you cant use goto" is also a limitation. It limits what you can do, but it does so for a very good reason. Limitations doesnt have to be a negative thing. All abstractions introduce limitations in some way, and FP/OOP are just two different schools of abstractions. If we want to be completely "unlimited" in everything we code we basically need to drop down to coding in assembler, with all of the risks that brings. – wasatz Jan 11 at 7:27
  • 1
    @wasatz That is actually a very good point. But it doesn't read that way in the answer. – Polygnome Jan 11 at 9:17
  • 1
    @wasatz Assembler? Think rather coding by using an e- beam to set the charges on the buried gates of the fets! Pretty sure that conceptually you can make the sand do things that way that the assembler does not support. It is abstractions all the way down, digital quickly becomes analogue when you get to things like memory sense amps or high speed signalling (See rowhammer for a case of breaking an abstraction), and analogue gives way to fields, waves and bandgaps, then statistical mechanics, and eventually it all winds up in a sea of quantum weirdness. – Dan Mills Jan 12 at 14:53
0

Apparently the author supposes that there are no runtime exceptions in functional programming languages such as Elm (hm... what does it really mean to be such as Elm, I wonder...) and that the picture the author shows says that there are no runtime exception in Elm. Both presuppositions are actually wrong.

  1. Programms written in functional programming languages can have runtime problems. The actual saying is something like "once it compiles, it usually works".
  2. There are runtime problems with Elm on that picture, we simply don't see them, because they are provided in a quite a small quantity (the speaker in the source talk says that).
0

This question is best answered with a couple of good examples. Other answers have somehow ended up focusing on division by zero, which IMO is a bad example.

Examples

Let's consider two of the most frequent exceptions you would see if you cracked open the data underlying that big bin of JavaScript exceptions. (Okay, actually they're the most common ones I see at my day job; but I imagine most JS developers would concur.)

Here's a simple object we'll use for our examples:

> person = { first: "Joe", last: "Biden"};
{ first: 'Joe', last: 'Biden' }

Cannot read property foo of undefined

When does this happen in JavaScript?

Let's say you haven't dealt with the person structure in a while, so you forget what its properties are. You want to know how many letters are in your person's first name, so you write:

> person.firstName.length
Uncaught TypeError: Cannot read property 'length' of undefined

How does Elm do it differently?

In Elm, unlike in JavaScript, you must first compile your code before you can run it. This has the side effect of converting the code from Elm (which browsers don't understand) to JavaScript, but its main benefit for the developer is that it analyzes the code you've written to ensure that it's self-consistent. In the analog to the above, the compiler is able to prove that the .firstName accessor cannot possibly succeed, so it refuses to generate any executable code and instead requires the developer to repair the problem.

foo is not a function

When does this happen in JavaScript?

This is similar to the other error, but in this case there is a property with the expected name -- but its value is not a function. Perhaps it's a String. Maybe your memory is really failing you and you're expecting there to be a last() function that returns the last person?

> person.last()
Uncaught TypeError: person.last is not a function

How does Elm do it differently?

As before, the Elm compiler will analyze the code and prove that the value in the .last property is not a function. It will refuse to produce any executable code and instead ask the developer to fix the code.

General Discussion

This kind of strong type checking is the main reason that Elm code throws virtually no runtime exceptions. The up-front analysis done by the compiler catches these errors before execution time.

Another very common source of errors in JavaScript code is null and undefined values. These concepts are simply not part of the Elm language. If you want a property to admit the possibility of "not having a value", it must use one of the "wrapper types" (Maybe or Result). So if some of your people have middle names and others don't, you would represent that explicitly:

person = { first = "Joe", last = "Biden", middle = Just "Robinette"}

But then the compiler will require you to handle the absence of a value[1]. And it will require all of your people to use a Just / Nothing construct when populating the middle property. In this way, it can preserve its proof that you'll never run across a "missing" value in the middle property without having code in place to handle it.

Neither strong type checking nor the absence of null or undefined are intrinsic parts of Functional Programming per se. But they are essential parts of what're usually called "Strongly-Typed Functional Languages". Elm and Haskell are two examples of such languages.

[1] Actually, Nothing is not an absent value, it's not a particularly special value from the compiler's point of view. It's simply one of the two kinds of values that inhabit the Maybe type (the other being Just).

0

How the Functional Paradigm or programming approach eliminates runtime exceptions?

They use special return values instead of the exception mechanism.

Note that this is a language-by-language choice and not something inherent to functional programing. For example, the (arguably) functional Lisp dialect used in Emacs has regular exception handling. It is no technical problem to implement classic OOP "throw-catch" style exception in a functional language.

Hence, the statistic you showed is just junk - yes, there may not be any exceptions in that particular language, but just because it doesn't implement them.

Are runtime exceptions a great disadvantage of OOP over Functional Programming?

There is no value to either, it just is.

Every programming language needs some way to deal with unrecoverable and unforeseeable errors like division by zero, out of memory conditions, network outages and so on.

In principle it would be possible to avoid any kind of exception in OOP programs as well, and just encapsulate everything in return values. In fact I have met plenty of programmers and programs in the past who are doing exactly this. A popular example is the value null in Java: you will find plenty of libraries and code which defines null as a valid return code in case of error.

Another example is NULL in SQL: SQL also uses this type to work with errors; for example at least in some dialects (Oracle) if you have NULL as part of any calculation, the result is always NULL. E.g. NULL + 5 = NULL instead of NULL + 5 = 0 + 5 = 5. Granted, this is a bit trivial, but nonetheless interesting and sometimes confusing for newbies to SQL.

If it is such a disadvantage,

It is not a disadvantage at all, it just is. There is nothing wrong with exceptions at all. Of course they can be misused, but then any kind of error handling can be misused.

why is OOP paradigm, programming approach and frameworks have been the industry standard? What is the technical reason? And what's the history behind this?

It's the most natural paradigm we discovered so far with a very gentle entrance for new programmers. From unstructured (assembly, old BASIC etc.) to modular (Pascal etc.) and then to OOP was a straight evolution of the imperative scheme; so all people who started at any point of time had a chance to easily end up on the OOP shores.

Also, thinking in objects is just very natural - we do it all the time. Sending messages between objects (i.e., method calls) is very easy to understand. The encapsulation of data and methods is also very simple. There are some a little more complicated concepts; i.e. some of the advanced patterns, or some implementation details in specific languages, but overall it is all quite accessible and you can get productive code with quite limited knowledge and effort.

Finally, these days most OOP languages have added plenty of functional constructs, either in the language itself, or through standard libraries. For example, the Ruby language, which is relatively young, specifically merges rock solid OOP basics with 100% inspection/reflection and very convenient and powerful functional features, enabling many of the "goodies" from pure functional languages, like lazy evaluation and other things. It's not a "functional" language by all means, but it absolutely scratches the same itch when you need it.

Compared to that, getting into the other paradigms (pure functional programming, logic programming like Prolog, constraint logic programming...) is quite hard for most people, at least in the cases where I witnessed it. You have to deeply understand how the "engine" of the language works, you have to grasp rather abstract concepts (for example, Monads in Haskell...)

The average programmer who is not a total nerd, just happened to end up in IT somehow, can pick up OOP stuff sooner or later (though I met plenty of fulltime, successful Java programmers who would be hard pressed to make really sensible choices of how to apply OOP in a useful manner in their programs) by the "fake it until you make it" scheme.

Which is not the worst that could happen - we have too few programmers in the world anyways for our current needs. If we would gate all that behind having to learn "real" functional languages thoroughly first, we'd be in deep trouble...

4
  • I'd say that thinking in pure OO is quite bizarre. We have integers; we don't make a list of things you can do with integers, with having to make a RichIntegers class to make a new function on an Integer, we don't write a.Add(b, c.Multiply (d))), etc. You can get about as far without learning the Liskov Substitution Principle in OOP as you can without learning monads in Haskell; that is, quite far, but you're going to get bit. If you go from Pascal to pure OOP, you're going to be lost. – prosfilaes Jan 13 at 1:30
  • For your example of Prolog or constraint logic programming, it's not objectively quite hard for most people. "Rover is a dog; Furry is a cat; Bites is a lizard; Rover, Furry and Bites are pets; dogs are mammals, cats are mammals, which pets are mammals" is trivial in Prolog and quite tedious in most OOP languages; you would have to understand more about the Prolog engine and reimplement that in your language than you'd have to understand about the engine to write that code in Prolog. – prosfilaes Jan 13 at 1:36
  • Alright, @prosfilaes, I have changed the word "objectively" to refer to my personal experience with the topic. Regarding integers - I think it's quite natural to think about operations (+, -, factorial, ...) and attributes of integers ("is it even, is it odd", etc.). Of course one would not want to write "5.plus(3)", but a modern language could very well transform "5 + 3" to a call of the method "+" of the class Integer. Opening the same up trivially for self-defined number types (i.e., arbitrary precision rationals). I always found it weird in Java that the basic types are not objects... – AnoE Jan 13 at 7:26
  • ... I mean, I get it - performance and all that. But still. – AnoE Jan 13 at 7:30

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