Are there any ways to recursively flatten tuples?

后端 未结 2 1615
醉梦人生
醉梦人生 2021-01-19 09:28

In Rust, is there any way to use traits and impls to (recursively) flatten tuples?

If it helps, something that works with N nested pairs is

相关标签:
2条回答
  • 2021-01-19 09:57

    I implemented this with specialization and auto trait.

    docs.rs

    github repo


    Usage is basically

    assert_eq!((1, (2, ((3,) (4, 5)), (), 6).flatten(), (1, 2, 3, 4, 5, 6));
    

    It removes all empty unit tuple () (like python).


    If you are writing generic code, you need to add

    where (A, B): Flatten
    

    rustc wants this because Flatten is implemented only if length of output tuple is smaller than 13.

    0 讨论(0)
  • 2021-01-19 10:04

    Maybe for certain small definitions of "flatten", but realistically not really.

    Start with the most specific implementation:

    trait FlattenTuple {
        fn into_flattened(self) -> (u8, u8, u8);
    }
    
    impl FlattenTuple for (u8, (u8, u8)) {
        fn into_flattened(self) -> (u8, u8, u8) {
            (self.0, (self.1).0, (self.1).1)
        }
    }
    

    Then make it a bit more generic:

    trait FlattenTuple {
        type Output;
        fn into_flattened(self) -> Self::Output;
    }
    
    impl<A, B, C> FlattenTuple for (A, (B, C)) {
        type Output = (A, B, C);
    
        fn into_flattened(self) -> Self::Output {
            (self.0, (self.1).0, (self.1).1)
        }
    }
    

    And then repeat for every possible permutation:

    impl<A, B, C, D, E, F> FlattenTuple for ((A, B), C, (D, (E, F))) {
        type Output = (A, B, C, D, E, F);
    
        fn into_flattened(self) -> Self::Output {
            ((self.0).0, (self.0).1, self.1, (self.2).0, ((self.2).1).0, ((self.2).1).1)
        }
    }
    

    These two implementations cover your two cases.

    However, you'd then have to enumerate every input type you'd like, probably via code generation. There's no way I'm aware of to "inspect" the input type and then "splice" it into the output type.

    You can even try to write something somewhat recursive:

    impl<A, B, C, D, E, F> FlattenTuple for (A, B)
        where A: FlattenTuple<Output = (C, D)>,
              B: FlattenTuple<Output = (E, F)>,
    {
        type Output = (C, D, E, F);
    
        fn into_flattened(self) -> Self::Output {
            let (a, b) = self;
            let (c, d) = a.into_flattened();
            let (e, f) = b.into_flattened();
    
            (c, d, e, f)
        }
    }
    

    But this will quickly run into base-case issues: the terminal 42 doesn't implement FlattenTuple, and if you try to impl<T> FlattenTuple for T you will hit conflicting trait implementations.

    0 讨论(0)
提交回复
热议问题