Существуют ли какие-либо способы рекурсивного выравнивания кортежей?

#generics #rust

#Ржавчина #кортежи #универсальное программирование

Вопрос:

В Rust есть ли какой-либо способ использовать trait s и impl s для (рекурсивного) выравнивания кортежей?

Если это поможет, то хорошим началом будет что-то, что работает с N вложенными парами

 trait FlattenTuple {
    fn into_flattened(self) -> /* ??? */
}

// such that
assert_eq!((1, (2, 3)).into_flattened(), (1, 2, 3))
  

Было бы еще лучше, если бы можно было расширить работу с любым типом вложенного кортежа таким образом, чтобы:

 assert_eq!(((1, 2), 2, (3, (4, 5))).into_flattened(), (1, 2, 2, 3, 4, 5))
  

Ответ №1:

Возможно для определенных небольших определений «сглаживать», но реально не совсем.

Начните с наиболее конкретной реализации:

 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)
    }
}
  

Тогда сделайте это немного более универсальным:

 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)
    }
}
  

А затем повторите для каждой возможной перестановки:

 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)
    }
}
  

Эти две реализации охватывают два ваших случая.

Однако затем вам пришлось бы перечислять каждый желаемый тип ввода, вероятно, с помощью генерации кода. Насколько мне известно, нет способа «проверить» тип ввода, а затем «объединить» его с типом вывода.

Вы даже можете попытаться написать что-нибудь несколько рекурсивное:

 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)
    }
}
  

Но это быстро приведет к проблемам базового варианта: терминал 42 не реализует FlattenTuple , и если вы попытаетесь impl<T> FlattenTuple for T , вы столкнетесь с конфликтующими реализациями признаков.

Комментарии:

1. Спасибо. Прежде чем я опубликовал свой вопрос, я попробовал несколько разных способов, но продолжал сталкиваться с проблемой конфликтующих реализаций. Например, реализация for (A, B) означает, что вы не можете реализовать для (A, (B, C)) и (A, (B, (C, D))) и так далее для решения N случая вложенных пар, и я не уверен, что ситуация изменится с предложениями where .

2. @lloydmeta: Да, эта конфликтующая реализация действительно раздражает : (

3. @MatthieuM. вы знаете, что для нашего кода гораздо лучше не быть двусмысленным. Возможно, специализация решит эту проблему, а может и нет…

4. @Shepmaster: Я согласен с отсутствием двусмысленности, но я так часто использовал C в прошлом, что специализация укоренилась в моем мозгу ^^