Is there an existing name for my higher-order function?

I have groups of functions that get called with the exact same arguments:

    a = func_a(arg1, arg2, arg3, arg4, arg5);
    b = func_b(arg1, arg2, arg3, arg4, arg5);
    c = func_c(arg1, arg2, arg3, arg4, arg5);

To reduce duplication, I have a helper function that accepts any number of arguments and returns a second helper function. When called, this second function applies the arguments it was constructed with to other functions:

    bar = foo(arg1, arg2, arg3, arg4, arg5);
    a = bar(func_a);
    b = bar(func_b);
    c = bar(func_c);

My question is, what's the best name for "foo" in this second example? I've looked for higher-order functions in various languages but I'm having trouble finding examples of my specific usage--there are similar functions but nothing exactly the same. I'm hoping to find a canonical name for what I'm doing (if one exists).

Like partial, foo() fixes the given arguments but it doesn't bind them to a specific function. The best name I can come up with is enclose() or callwith(). If anyone know of an already-existing name for this, I'd like to hear what it is.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

I don't know, but ...

I generally consider your original situation a bad code smell, and apply the Introduce Parameter Object refactoring. That's probably better from a software engineering point of view.

If you're asking this out of general interest in higher-order functions, or if you don't have control over func_a, func_b and func_c, then I guess my comment is not terribly useful. :-)

This is 100% correct, in a

This is 100% correct, in a very precise technical sense. The proposed foo function is the Church encoding of a tuple.

Let's look at the definition of bar (cut down to pairs for simplicity):

   bar = fun k -> k arg1 arg2

will have the type ∀c. (A → B → c) → c (assuming arg1 has type A and arg2 has type B). In a functional language this is provably equivalent to just having a pair containing arg1 and arg2. This is due to parametricity, and arises as one of Wadler's "theorems for free".

So if I wrote a function foo

   foo a b = fun k -> k a b 

I would prefer to call it makePair or something similar. However, I would try not to write such a function unless my language lacked support for tuples -- higher-order code is harder to understand than first-order data.

Which is just the same as saying it's a bad code smell, and you should try to introduce a parameter object if you can.

I agree with your assessment but

I agree with your assessment but I am asking in the more general sense (although my question is inspired by a piece of middleware I'm working on).

passTo

I've written this once or twice. I think I settled on calling it passTo. The expression passTo(arg)(func) passes arg to func.

Before settling on that, I remember considering callWith like Scheme's call-with-current-continuation. Since you came up with this too, and since Scheme has that kind of naming, maybe it's more common.

curried apply?

Am I misunderstanding, or is this essentially a curried form of apply?

John, I think you're right!

John, I think you're right! It is essentially a right-curry'd apply--or right-partial'd apply, depending...

Perhaps applywith() might be appropriate.

In any case, I'm really glad to get this feedback. Thanks, everyone.

Currying

Currying is something you do to types. Specifically the transformation of a multi argument function into several single argument function applications in the type signature. Note, it does not imply partial application, it is a way of simplifying the implementation of type systems. For example:

f :: a * b -> c => f :: a -> b -> c

Partial application is applying some but not all arguments to a function, so it does not seem right here either. Right partial application would bind the rightmost arguments first

This transformation is:

f1 :: a * b * c * d * e -> r1
f2 :: a * b * c * d * e -> r2

g :: a * b * c * d * e -> ((a * b * c * d * e -> r) -> r)

Which doesn't look like either of those. I don't get what "right currying" would be, but right partial application would just be binding the rightmost argument first, which does not seem to fit the above signature for 'g'.

I've never encountered that usage

In any discussion I've read or heard, Currying has nothing to do with types [non-overlapping sets of discussions?]; it's simply a technique for breaking up an n-ary function into a chain of binary functions. It'd be more historically accurate to name the technique after either Frege or Schönfinkel, but then, there's a long academic tradition of naming things after people not responsible for them.

For these Kleinian groups Poicaré obtained new automorphic functions, [...] which he called Kleinian functions. These functions have properties analogous to the Fuchsian ones [...]. Klein had considered Fuchsian functions while Lazarus Fuchs had not. Klein therefore protested to Poicaré. Poicaré responded by naming the next class of automorphic functions that he discovered Kleinian because, as someone wryly observed, they had never been considered by Klein. — Morris Kline, Mathematical Thought From Ancient to Modern Times

Currying does not imply partial evaluation

Yes, you are right, I was perhaps thinking about the difference between partial application and partial evaluation, and that currying does not imply partial evaluation. It does of course imply partial application.

I can't quite make right currying work though, it would seem to get stuck with a single argument, I can pass the first argument to a function 'f' and I can pass 'f' to 'g' but I can't get any extra arguments in there. I suppose its needs a pair, and the operator would be make pair, then you can pass f(makePair(makePair(a, b), c))

In this case the function 'foo' constructs a new function 'bar' that applies a tuple to its argument. So 'bar' would be "applyTuple" and that would make 'foo' something like "makeTupleApplicator" :-)

Perhaps right-apply can be

Perhaps right-apply can be overthought. A notion I've been developing for some time is that the core flaw of most programming languages is trying to mediate a dialog between human programmer and computer, which is guaranteed to stifle the human because dialog between humans takes place at a sapient level whereas, to communicate with a computer, the human has to sink to the computer's nonsapient level. (In contrast, mathematics is traditionally a dialog between human mathematicians.) Currying is first-and-foremost a technique used by humans; the moment you try to bring it down to the computer's level it starts to get difficult. In this particular case, apply is a function of exactly two arguments, and it's straightforward for a human to break it down right-to-left:

($lambda (args) ($lambda (func) (apply func args)))

or, to avoid the explicit use of apply,

($lambda (args) ($lambda (func) (eval (cons func args) (make-environment))))

Alternatively,

let's say we want a function right-curry, that takes a single argument and returns a function that, when called, will take one or more elements and tack that single argument onto the end. So we can write (f x y z t) as ((right-curry t) f x y z).

($define! right-curry
   ($lambda (arg)
      ($lambda left
         (eval (append left (list arg))
               (make-environment)))))

We could also rewrite (f) as ((right-curry f)).

[You're quite right, of course, that this requires meta-programming capability, incarnated here by eval]

Better


($define! right-curry
   ($lambda right
      ($lambda left
         (eval (append left right)
               (make-environment)))))

Which allows things like ((right-curry z t) f x y).

Single argument

I see where you are coming from, but isn't currying a transform so that all functions take a single argument? Look at the types of curried languages functions can only take a single argument. I understand how you are using right currying, but it does not quite seem to fit, because you need a multi argument function at some point.

I would argue there are no right or left curring transforms. There is in fact a single "curring" capable of transforming a program with multi-argument functions into entirely functions that take a single argument. Hypothesis: In the universe of lambda calculus there is one and only one minimal transform capable of taking a program with multiple argument functions and returning a program using only single argument functions. By minimal I mean without adding non-operations like the identity function.

I accept that people can be fuzzy in their thinking, but from my experience subjecting that thinking to the rigour of a programming language has in every case exposed the weakness of such thinking and resulted in either an improvement in clarity or the rejection of the idea.

But, I'm not talking about

But, I'm not talking about fuzzy thinking; I'm talking about sapient thinking. As a civilization we've developed one conceptual framework for placing ourselves in a hierarchy where a Deity is above us and mechanisms are below us, and another conceptual framework in which broadside techniques such as evolution and "big data" are essentially above us; the trouble, I've recently concluded, is trying to order these things. We're neither above nor below the broadside techniques; there are things sapience can do that broadside techniques can't, and vice versa. But we've got no conceptual framework for describing the things that each of these techniques can do that the other can't, only for portraying one as "better" or "smarter" than the other.

apply-with-args?


(define (apply-with-args . args) (lambda (f) (apply f args)))
((apply-with-args 1 2 3) +) => 6

Overdue Follow-up

I recently prepared the next release of the project I was working on when I made this original post. I remembered that I posted here a long time ago but never followed up after settling on a solution. This is for a Python project called datatest (https://pypi.org/project/datatest/). I didn't use the helper function I was originally toying with. My solution was to create a container class called RepeatingContainer.

To revisit the original problem, this first example demonstrates the type of situation I want to avoid (the same operations are duplicated for each Pandas DataFrame):

import pandas as pd

df1 = pd.read_csv('file1.csv')
df2 = pd.read_csv('file2.csv')

columns1 = df1.columns
columns2 = df2.columns

counted1 = df1['C'].count()
counted2 = df2['C'].count()

filled1 = df1.fillna(method='backfill')
filled2 = df2.fillna(method='backfill')

summed1 = df1[['A', 'C']].groupby('A').sum()
summed2 = df2[['A', 'C']].groupby('A').sum()

maxval1 = df1[df1['A'] == 'x']['C'].max()
maxval2 = df2[df2['A'] == 'x']['C'].max()

Using a RepeatingContainer, I can write each operation once. When operations are performed on the container, they are forwarded to the objects it contains. Then, I unpack the return value to get the individual results. The following code does the same thing as the previous example, but I don't have to repeat myself:

import pandas as pd
import datatest as dt

repeating = dt.RepeatingContainer([
    pd.read_csv('file1.csv'),
    pd.read_csv('file2.csv'),
])

columns1, columns2 = repeating.columns

counted1, counted2 = repeating['C'].count()

filled1, filled2 = repeating.fillna(method='backfill')

summed1, summed2 = repeating[['A', 'C']].groupby('A').sum()

maxval1, maxval2 = repeating[repeating['A'] == 'x']['C'].max()

And RepeatingContainer is generic, there's nothing DataFrame-specific about it. Here's an example using strings:

>>> from datatest import RepeatingContainer

>>> repeating = RepeatingContainer([
>>>     'one',
>>>     'two',
>>>     'three'
>>> ])

>>> result = repeating.upper()
>>> result
RepeatingContainer([
  'ONE',
  'TWO',
  'THREE'
])
>>> result = 'Number: ' + repeating
>>> result
RepeatingContainer([
  'Number: ONE',
  'Number: TWO',
  'Number: THREE'
])

>>> str1, str2, str3 = result  # Unpack items.
>>> str1
'Number: ONE'
>>> str2
'Number: TWO'
>>> str3
'Number: THREE'

Here's the entry for RepeatingContainer in the reference documentation:
https://datatest.readthedocs.io/en/stable/reference/data-handling.html#repeatingcontainer

And for the very curious, here's the implementation:
https://github.com/shawnbrown/datatest/blob/master/datatest/_vendor/repeatingcontainer.py