Is there a way to untuple function parameters?

Let's say I have a tuple of things that I want to pass to a function, is there a way to do that without explicitly unwrapping the elements of the tuple?

In other languages, such as Scala and Haskell this would be called untupling. A common case is that I have extracted optional values from somewhere and I want to map over the scenario where they all exist (this could also be accomplished with a match but I find a zip can be cleaner for this simple case, which would result in nested tuples if we extended it to higher arities, so zip2/zip3/zipN etc often exist)

let oa = get_optional_thing("a", map_thing);
let ob = get_optional_thing("b", map_thing);

oa.zip(ob).map( |(a, b)| MyThing::new(a, b))

in this last line I would prefer to be able to write

oa.zip(ob).map(MyThing::new)

is that possible in any way without me manually creating a function that takes the tuple as a parameter?

(we could also imagine doing it more like (oa, ob).apply2(MyThing::new) if we were to copy the Haskell Applicative esque suite of functions)

Rust doesn't do this automatically. Functions taking N arguments are distinct from functions taking an N-tuple.

However, you can create generic functions and use them as:

oa.zip(ob).map(untuple(MyThing::new))
5 Likes

Though @paramagnetic's answer is fine, I'd like to add another solution. You can create an extention trait, which will allow you to pass your mapping function directly, without wrapping it in untuple:

trait UntupleMap2Ext<T1, T2> {
    fn untuple_map2<U, F>(self, f: F) -> Option<U>
    where
        F: FnOnce(T1, T2) -> U;
}

impl<T1, T2> UntupleMap2Ext<T1, T2> for Option<(T1, T2)> {
    fn untuple_map2<U, F>(self, f: F) -> Option<U>
    where
        F: FnOnce(T1, T2) -> U,
    {
        self.map(|(t1, t2)| f(t1, t2))
    }
}

fn main() {
    let x = Some(3);
    let y = Some(4);
    let f = |a, b| a + b;
    
    assert_eq!(x.zip(y).untuple_map2(f), Some(7));
}
1 Like

Great Solution. I want to add two more approaches for higher-order tuples which still have generic interfaces.
They can be used as in this example:

fn needs_two_tuple(a: usize, b: &str) {
    println!("{} {}", a, b);
}

fn needs_three_tuple(a: usize, b: &str, c: isize) {
    println!("{} {} {}", a, b, c);
}

fn main() {
    untuple!(needs_two_tuple((3, "home")));
    untuple!(needs_three_tuple((208, "cellular", -734)));
    
    needs_two_tuple.untuple((3, "home"));
    needs_three_tuple.untuple((208, "cellular", -734));
}

The macro-based approach has the drawback that all arguments need to be written out explicitly and the second requires the argument as an immediate input.
Unfortunately I could not get an identical version as @paramagnetic to work for higher-order tuples by using a trait imp as in my second approach.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.