FPOO Ch. 9: Functions That Make Functions

Note: This article is part of a series working through the exercises in Brian Marick's "Functional Programming for the Object-Oriented Programmer", in Elixir instead of Clojure. The full notes and source code are on GitHub.

Unlike Clojure, as far as I know Elixir does not have a library of foundational higher-order functions such as like lift or complement. So I’ll have to build them myself.

Before I do anything else, I need a helper function for adapting the arity of anonymous functions. This is because Elixir has no support for variable-arity functions. As a result, the only workable way I can find to build generic functions involves always returning a function of a single argument, where the single argument is a list of the actual arguments. Which can then be used with apply, etc.

This works, but it’s pretty unpleasant to have to call all generated functions as, e.g. add2.([2]) instead of add2.(2). Not to mention that in order to be composable, any function-modifying-function would have to have two versions: one that accepts a “normal” function of N arguments, and one that takes a function of single argument list.

So instead I define adapt_arity, which takes a function of one argument and an arity, and returns a function of arity arguments.

I spent a few days off and on trying to come up with a clean macro version of this, but I completely struck out. The problem I kept running into is that the macro is evaluated at compile time, but the arity number is only discovered at runtime. Eventually I reluctantly settled on using Code.eval_string instead.

(I even tried a version that eval-ed code at compile time to generate 256 different versions of adapt_arity, but then I ran into an issue where I couldn’t get the Code.eval_* functions to eval in the context of a given module. Instead they were evaluating in the global(?) context, and functions can’t be defined outside a module.)

UPDATE: Where I failed, meh succeeded. Check out this masterful rewrite using macros instead of evaluation: https://gist.github.com/meh/7990856/c59f69216418e27bd01f43f47262af6870c8874a

UPDATE: José Valim weighs in with an unrolled version that actually works, unlike my attempt: https://gist.github.com/josevalim/ea084b59f88de1ab6d35

Here’s the gist from meh, updated with the compile-time unrolling approach: https://gist.github.com/meh/7990856

Given a call like this:

The following anonymous function will be returned from adapt_arity:

I also define a helper function to discover the arity of a given anonymous function.

The magic number 255 corresponds to the upper bound of arguments an Erlang function can take.

So far as I can tell there’s no built-in way to check the arity of a function. This was the best I could come up with. I suspect this could be made more efficient by unrolling it into a pattern-matching version, if someone really wanted to.

UPDATE: On IRC, meh pointed me to the fun_info, which simplifies this function considerably.

Now on to the various function adapters. First up, partial.

Next, complement.

And now lift, which effectively turns a function into a function modifier.

Finally, comp, to compose N functions together.

The compact code for comp doesn’t really reflect the amount of brain pain that went into working it out. For some reason I have a hard time reasoning about reductions involving function composition.

Oops, one more: juxt.

Exercise 1 challenges me to write a function that adds 2 to each element of a sequence, using a point-free style (no fn allowed).

This would be easy enough with just the capture (&) operator, but since I’ve got a shiny new partial function, I’ll use it.

This might draw the question “if the capture operator would have worked just as well, what’s the point of partial?” The answer is genericity. partial can be used within other functions where the number of partial arguments to be supplied isn’t known until runtime. That’s not possible with captures.

Exercise 2 is to write a separate function using juxt.

Exercise 6 is to write always, a function that generates functions that always return a constant value regardless of arguments. As usual, Elixir complicates things by requiring an explicit arity.

Exercises 7 and 8 deal with validating ISBNs.

And that’s enough for now.