Writing functions that take and return closures


#1

I’m trying to write a function that takes in a function representing a binary operation over values of some type T, and returns a function that takes two &[T]'s, and returns a Vec<T> containing the result of applying the binary operation pairwise over the input slices.

In essence, this function ‘lifts’ a binary function over T's to operate on slices of Ts. One use case for this would be to allow operations like addition and multiplication to be performed pairwise over vectors. For example:

let pairwise_add = pairwise(|x, y| x + y);
let a = vec![1,2,3];
let b = vec![2,3,4];
let result = pairwise_add(&a, &b);
// result should contain: [3, 5, 7]

So far, this is what I have:

fn pairwise<F, T>(op: F) -> Box<Fn(&[T], &[T]) -> Vec<T>> 
        where F: Fn(T, T) -> T, T: Copy {
    Box::new(move |u, v| {
        u.iter()
         .zip(v.iter())
         .map(|(x, y)| op(*x, *y))
         .collect()
    })
}

However, this fails to compile because “the parameter type F may not live long enough”. One workaround I’ve found so far is to change op to &'static F. This allows the function to compile, but now the original closure is forced to have a static lifetime, which means we can’t just pass a closure to pairwise. (i.e., this fails to compile: pairwise(&|x, y| x + y))

Is there a way to get this to work?

More generally, is it possible to define a function that takes in a closure argument and uses that closure within another closure which is then returned? Or are these kinds of higher-order functions discouraged?


#2

This works:

fn pairwise<'a, F, T>(op: F) -> Box<Fn(&[T], &[T]) -> Vec<T> + 'a>
    where F: Fn(T, T) -> T + 'a,
          T: Copy
{
    Box::new(move |u, v| {
        u.iter()
            .zip(v.iter())
            .map(|(x, y)| op(*x, *y))
            .collect()
    })
}

The problem is that by not specifying a lifetime in a Box, it automatically assumes that the Box can be used indefinitely ('static). However, the closure you are passing to the pairwise function could have internal references that may not be 'static. So by introducing a lifetime 'a and restricting the Box’s lifetime to the lifetime of the closure, everything works just fine :slight_smile:


#3

Using the unstable impl Trait syntax, you can avoid the Box and thus the allocation and dynamic dispatch: https://is.gd/DuCRc3

I’m on mobile so the formatting is crap, but it works.


#4

Ah, that makes sense. Thanks for the explanation!