Functional Programming – What is Control Abstraction

cfunctional programmingobject-oriented

I understand concept data abstraction as it is relevant to OO programming. However on contrary it seems Function Programming promotes or makes use of concept control abstraction.

I tried searching around blogs but could not find any relevant or easy to digest (at least to me) answer. Appreciate if someone can explain what control abstraction means in context of functional programming with some good example.

One reference to the topic of control abstraction was found here, but I would appreciate it if someone could summarize or restate the concept.

Best Answer

If a language supports control abstraction, this means that we can define our own control flow constructs. While this can certainly be abused and make a program totally incomprehensible, it can also make a program much clearer.

Preconditions for control abstraction are higher order functions, closures, and lambdas, although many abstractions are inconvenient without first-class continuations. Note that objects are in some ways equivalent to closures, so you can use some aspects of control flow abstraction in non-functional languages as well – though typically with more confusing syntax.

As an example, let's consider a using block in C# (or try-with-resource in Java, or with in Python). Here, the languages implemented special syntax to represent this control flow:

  • a resource is acquired
  • a block is executed
  • when control leaves the block, the resource is closed.

But with higher order functions, we could have implemented this ourself!

static void using<T>(T resource, Action<T> body)
where T: IDisposable {
  try {
    body(resource);
  } finally {
    if (resource != null)
      resource.Close();
  }
}

This could then be used like:

using(new File("foo.txt"), file => {
  ...
});

So control abstractions let us evolve the language with user-defined control constructs, just like data abstraction lets us create user-defined data structures.

Another nice example is a method cascade operator, that lets us perform some side effects in a method call chain:

static T Tap<T>(this T instance, Action<T> body) {
  body(instance);
  return instance;
}

That looks pointless, but can sometimes be quite convenient to configure objects in a fluent interface, without having to extract them into a variable or separate function.

return new Foo()  // where Foo is a reference type
  .Tap(f => f.X = 7)
  .Tap(f => f.Y = 18)
  .Tap(f => f.Name = "Foo Bar");

There are a few notable uses of control abstraction:

  • Promises in JavaScript (and tasks/futures in C#) can be seen as a control abstraction that allows you to structure async code without deeply nested callbacks. However, without first-class continuations, we still need callbacks for promises. With continuations, async/await could be implemented as a library.

  • Embedded DSLs benefit greatly from control flow abstraction. E.g. the flexible syntax of Scala or Ruby lends itself very well to such uses. For example, a parser combinator library lets you assemble a parser in your source code, but interprets this parser using its own control flow as an input document is parsed.

  • Monads and Functors in Haskell and Scala can be interpreted both as data abstraction and control abstraction. A simple monad is Maybe or Optional (similar to Nullable in C#). This essentially gives us a Safe Navigation Operator (like ?. in C#) except that it can be implemented as a library, without having to wait for the core language to change. In C#, IEnumerable is a bit functor-like with the Select method.

Nowadays, most languages support some kind of control flow abstraction because it allows users to build great libraries and be productive, without having to wait for the language to add a missing operator. But there are some holdouts:

  • C doesn't support any control abstractions beyond macros. You can in theory implement closures or objects with function pointers, but that isn't exactly convenient syntax.

  • Go supports very few opportunities for control abstraction. It does technically have closures, but it doesn't have generics which are necessary for reusable abstractions. Only built-in types are generic, so you can recreate a few control abstractions using channels and for-loops.