3

I am trying to figure out how to apply two functions in a sequence in Rust, where the first function returns a tuple. So something like:

let f = |x,y| { x+y; };
let g = || (1,2);
println("{}", f(g()));

This won't work. Even if replace g() by a 2-element tuple.

In Julia I could do f(g()...) to expand the tuple into function arguments.

I figured I could define a .apply method to do the expansion for me. The following works, but has to be manually or by macro written for every number of arguments:

trait Apply2<A,B,C>: Fn(A,B) -> C {
    fn apply(&self, _ :(A, B)) -> C;
}

impl<T,A,B,C> Apply2<A,B,C> for T where T: Fn(A,B) -> C {
    fn apply(&self, (a, b) : (A, B)) -> C {
        self(a, b)
    }
}

// and then…

println!("{}", f.apply(g()));

However, the manual/macro-based writing of the traits and implementations is somewhat painful. I guess this should also work:

trait Apply<Args>: Fn<Args> {
    fn apply(&self, _ :Args) -> Self::Output;
}

impl<T,A,B> Apply<(A,B)> for T where T: Fn<(A,B)> {
    fn apply(&self, (a, b) : (A, B)) -> Self::Output {
        self(a, b)
    }
}

However, this is not allowed in "stable" and I'm not going to switch to nightly builds at this point.

Any better ideas? Any plans in Rust to better handle this situation?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • You can't do that without nightly, unfortunately. – Chayim Friedman Dec 24 '21 at 07:06
  • 1
    And with nightly, you don't need a custom trait - you can just call `f.call(g())`. – Chayim Friedman Dec 24 '21 at 07:08
  • See also [Draft RFC: variadic generics](https://github.com/rust-lang/rfcs/issues/376). – Chayim Friedman Dec 24 '21 at 07:09
  • Background: I'm evaluating to see if I will switch to Rust for numerical computing, having decided that the dynamic typing of Julia is sinful after having spent hours on reducing memory allocations due to severe type system issues. For similar “wasted hours for performance” reasons I'm currently ranking Rust over Haskell. Other things so far on my Rust wishlist would be support for custom operators (bitwise by contrast seems an obsolete edge case in 2021), esp. for elementwise ops, and better support for higher-dimensional objects (`[i, j]` shorthand for `[[i,j]]`, general `Set`s in match). – 497e0bdf29873 Dec 24 '21 at 07:19
  • 2
    Duplicate of https://stackoverflow.com/questions/39878382/is-it-possible-to-unpack-a-tuple-into-function-arguments (not voting to close because the question here goes further and to allow macro based answers as they look natural). The answer on the other QA mentions `std::ops::Fn::call`. – Denys Séguret Dec 24 '21 at 07:25

1 Answers1

1

You can't do that exactly in stable, but you can cover most of the functions with a macro:

trait Apply<Args> {
    type Output;
    fn apply(&self, args: Args) -> Self::Output;
}

macro_rules! impl_apply {
    // Empty case
    () => {};
    ($first_generic:ident $($other_generics:ident)*) => {
        impl_apply!($($other_generics)*);

        impl<$first_generic, $($other_generics,)* Ret, Func>
            Apply<($first_generic, $($other_generics,)*)>
            for Func
        where
            Func: Fn($first_generic, $($other_generics,)*) -> Ret,
        {
            type Output = Ret;
            #[allow(non_snake_case)]
            fn apply(
                &self,
                ($first_generic, $($other_generics,)*): ($first_generic, $($other_generics,)*),
            ) -> Self::Output {
                self($first_generic, $($other_generics,)*)
            }
        }
    };
}
impl<Ret, Func> Apply<()> for Func
where
    Func: Fn() -> Ret,
{
    type Output = Ret;
    fn apply(&self, (): ()) -> Self::Output {
        self()
    }
}
impl_apply!(A B C D E F G H I J K L M);

Playground.

This macro covers all functions up to 13 arguments.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77