DEV Community


Posted on • Updated on

Functional program boundaries

This post is part of a series called functional fundamentals. See the introduction and index. That's also where you can request specific topics

Note that I am not an authoritative figure on these subjects, if you see something you think might be incorrect or incomplete, please point it out with a comment, let's prevent mistakes from spreading :-)

By this point in the series you should already know a lot about the advantages of (pure) functional programming:

  • Safe composition (easy refactoring, guaranteed integration, real monads...)
  • Strong type systems (no run-time errors, no backdoors)
  • Explicit data-dependencies (good for various optimizations)

And now it's time to talk about the catch.
Functional code doesn't do anything. It's just a value. Even readLn "Hello, World!" (the print("Hello, World!") equivalent in Haskell) is just a value of type IO ().

The program needs to be evaluated, it needs to be applied to inputs at the very least. It needs to interact with the world, which unfortunately, is not functional, at least not in a way that is useful to us.

In places where this happens, we have a functional program boundary, where the functional world ends and the imperative one begins. Haskell crosses this boundary using do blocks (EDIT: this is not entirely accurate, see comments), Elm has The Elm Architecture which is specific to websites.

On the boundaries, run-time exceptions become possible, composition is non-trivial and the type system is often more generic.

The larger the boundaries, the less use you will have out of functional programming.

Boundaries can be shifted however. Functional programming only cares about data transformations, not so much where or when they happen.

Big data applications have leveraged this by extending the boundary over a network of computers rather than single ones. AWS lambda and Google Functions are using a similar concept, treating imperative programs as pure functions. In fact, mapping a pure function over an array, list or dict is embarrassingly parallel.

Elm, using TEA, instead hides the imperative world by segmenting the problem into functional parts, abstracting away the imperative glue into commands and messages. This way the programmer is only bothered with the functional stuff.

In short: how a functional language, library or framework represents a problem can greatly affect how large your functional boundaries are, smaller boundaries generally being better.

Further reading:

Top comments (3)

swizzard profile image

the boundary in haskell is main, not do, although that has as much to do with laziness as fp.

drbearhands profile image

I wasn't as accurate as I should have been in my post.

You're right in that main crosses the border of your program. do blocks are just syntactic sugar for effectfull monads.

However, if you're using monads as effects you are essentially doing imperative programming. A single, isolated, effectfull monad, such as Maybe used as the result division by 0, I would not consider imperative yet, but large do blocks full of them I would definitely consider part of the functional border.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.