A New Idea for Calling Functions
Disclaimer: This is going to come off a bit ranty. I’m not as frustrated by the “problems” I bring us as it sounds; rather, it is used to emphasize why my thought processes did what it did. I’m not even going to be using header titles, which is weird for me :)
I’ve had a recent thought process about calling functions. It has been ceaselessly frustrating to me how functional languages (and even other languages at times) accomplish a certain goal.
That goal is chaining calls.
Nobody seems to get it right, in my opinion. The only time it works well is with fluent interfaces of objects. Let’s take a simple example of filter
ing a list (called myList
) by even numbers (the predicate function will be called evens
) then map
ping to the result squared
:
In a typical multi-paradigm language with generic functions for filter
and map
, such as Python:
map(squared, filter(evens, myList))
Or a lisp language:
(map squared (filter evens myList))
The first thing that pops out to me is that the functions run from right to left, inside out. Moving from the inside out isn’t too difficult for those who work with math a lot, but we still prefer to do it from left to right (those whose primary language is a left to right language, anyway). Having to reverse these function calls in our heads is tedious and slows our ability to determine what the code does.
Side Note: Also, note that these functions put the passed-in functions first in the argument list instead of the list. I’ve personally always thought it best to put the primary object that is being worked on first. The only reason I can think of for including the functions first is that it provides for easier-to-create higher-abstraction functions (such as making a sum
function from reduce
) through currying or using Python’s partial
. The order of arguments will come up again later in the “solution”.
One helpful thing that a lot of these languages provide is a composition function, which allows us to combine the function calls into a single one. If we curry or partial
ize the map
and filter
functions to map_squared
and filter_evens
, respectively, we can make a compose call like this:
compose(map_squared, filter_evens, myList)
Or like this in a lisp:
(compose map_squared filter_evens myList)
Notice anything? We’re still reading right to left! For some reason, it was generally agreed upon that compose(f, g, data)
should read as “f
of g
” (like a mathematician) instead of “f
then g
” (like any other human being). Granted, functional programming (and probably 90% of other programming) was designed by those who are interested in math. Heck, I LOVE math, having taken Calculus I twice (the first time in high school, the second in college, both times for fun). But this is still not a way of reading and writing function calls that I like.
What do I like? As mentioned earlier, fluent OO APIs work beautifully for this. Ideally, I’d like to call
myList.filter(evens).map(squared)
Some might recognize this as being similar to how Java 8’s Stream
API looks. This is very close to how Kotlin looks, too.
Here are the problems with the OO way, though:
- OO is only available for OO languages (obviously)
- To accomplish these fluent APIs, you either have to build them into objects initially, or you have to create some sort of wrapper class that implements them
- Even if you do have a fluent API, some new function may be wanted later that can’t be added for some reason.
The last two problems are alleviated with extension methods (which are only included with OO languages for obvious reasons), but I’m deciding to come from the point of view of a language that is either purely functional or is at least partially OO without a way to create extension methods.
My only solution is to create a new, optional way for functions to be called. It was partially inspired by Python’s methods which already do this, but do so in reverse; they allow object methods to be called like normal functions, whereas my solution allows normal functions to be called like object methods. For an explanation of how Python does the former, see this article on why I think the self
argument was a stroke of genius.
So, if you haven’t figured it out, my solution is to allow the first argument of function to act like it’s an object that you’re calling the function (as a “method”) on. In this way, you could take a lot of Python’s built-in functions, like len
, iter
, next
, etc and call them like this:
myList.len() myList.iter() myIterator.next()
This is more in line with other OO languages.
They’re not truly methods, but they look like methods, and it allows them to be chained like fluent methods. Going back to my side note earlier, we’d have to reverse the argument order on filter and map (and the others like them) to have the sequence be first. Otherwise, our example wouldn’t really work, looking like this:
squared.map(evens.filter(myList))
Which doesn’t even begin to solve the problem.
What about currying or using partial
? Well, it looks like you’ll have to manually define your sum
function, rather than using currying. For example (assuming reduce takes a list first now):
def sum(sequence): return reduce(sequence, operator.add)
As for partial, it still works if you use the keyword argument:
sum = functools.partial(function=operator.add)
What about lisps? They don’t have an OO call syntax. How could this possibly work with lisps? Simple: the new way can put the first argument first, then the name of the function being called, prefixed with a period. For example, the new map and filter syntax would look like this:
((myList .filter evens) .map squared)
So… what do you think? Do you like the idea? I would love to make a language that has this functionality, but I’m too busy with other ideas to let myself be bothered with creating a new language. I’d prefer it if languages that are currently out there were to add this syntactic sugar to their repertoire. Fingers crossed. Cuz I’m getting sick of writing wrapper classes for this type of stuff.
Reference: | A New Idea for Calling Functions from our JCG partner Jacob Zimmerman at the Programming Ideas With Jake blog. |