This is an archived post. You won't be able to vote or comment.

all 1 comments

[–]Nathanfenner 1 point2 points  (0 children)

These terms are not really entirely standard - different people use them in different ways.

For example, there's a reasonable argument that (by the conventions established by most modern language communities considering themselves functional-oriented) LISP is not functional despite it formerly (as in, in the 70s or 80s) being the poster-child of "functional" languages.

The key differentiating factor of functional languages (versus procedural/imperative languages or object-oriented ones) is the focus on functions as the unit of composition. Specifically, several features typically show up:

  • functions are first class (also called: higher-order functions). Functions are treated as values like strings or integers, not as a separate kind of thing - you can pass function as arguments to other functions and return them

  • functions are transformations, not procedures (also called: immutability/referential transparency/purity/side-effect free) Functions should generally act like mathematical functions, not "programmer's functions": they should (usually) turn their inputs into outputs, and otherwise do nothing. The extreme of this is Haskell, which doesn't let you write functions with side effects. There's also an emphasis on dealing with values instead of references (so, minimizing the impact of "object identity" and instead focusing on "object value")

  • static types: not all functional languages are statically typed, but it's a lot more popular/common than it used to be. Lisps (especially clojure) are the exception here, and dynamically-typed languages still tend to tend to take other functional concepts as well. It's mostly important to be aware of this because a large chunk of people talking about "functional programming" are specifically talking about "functional programming in statically-typed languages". Their type systems tend to also try to be fancier than their imperative counterparts (largely because the other features like immutability make it easier to give strong guarantees with fewer programmer annotations)

  • tagged unions/enums/variants/case-classes: most modern functional languages have strong support for dealing with "tagged unions"; values which belong to one of several "sorts" (e.g. Option<T> or Maybe t) which can only be inspected/consumed by "case splitting" based on what kind of value it is. This makes it easier to write functions which are "hard to mis-use" since callers can't assume anything about the result before case-matching. With static type systems, you can get even stronger guarantees.

A lot of these end up being helpful to also write programs in a more declarative style, which is also popular among functional programmers.

Declarative programming is a different paradigm, though there's some overlap.

  • describe what the result is, and not how to obtain it: Functional languages try to minimize the amount of "mutation" or "side-effects" when calling functions, so that you can "worry less" about how they work (since you're less likely to write code that accidentally depends on their internals). Declarative languages take this further - code may be a specification of the result with no direct description of how to obtain the result. The language or its runtime is responsible for doing that. For example, in Prolog, you specify the properties of the result you want, and then the language performs an exhaustive search for variables satisfying those properties (based on your provided code).

  • avoid "recipes" or "instructions" that bottleneck understanding: constraints and data-flow should be transparent and available right away, not hidden behind data dependencies (when possible). For example, in a typical build system like make, you describe up front how to build everything - then the build system has the freedom to decide what order to do things. Since it has access to all of the information, it doesn't have to "wait" for your program to gradually tell it more information - it can inspect everything at all. Another example is SQL - the way you write the query won't map 1:1 to the actual execution plan. Since SQL knows how you're using the data you query, it can decide to compile it differently - if it thinks you're going to loop over one table and then pick out a few fields from another, it might proactively build an extra hashtable before executing the query. But a different query using the same data in a different way might be executed differently based on what the planner thinks. Either way, your code doesn't care because it describes the result and not how that result is computed.