2015-03-19 13 views
9

Moim początkowym problemem było przekształcenie krotki różnych typów w ciąg znaków. W Pythonie, byłoby to coś jak:Jak iterować lub odwzorowywać krotki?

>> a = (1.3, 1, 'c') 
>> b = map( lambda x: str(x), a) 
['1.3', '1', 'c'] 

>> " ".join(b) 
'1.3 1 c" 

Jednak Rust nie obsługuje mapy na krotki - tylko na konstrukcji wektora podobny. Jest to oczywiście spowodowane możliwością pakowania różnych typów do krotki i braku przeciążenia funkcji. Ponadto nie mogłem znaleźć sposobu na uzyskanie długości krotki w czasie wykonywania. Tak więc, jak sądzę, do przeprowadzenia konwersji potrzebne byłoby makro.

Na początek, starałem się dopasować czele krotki, coś takiego:

// doesn't work 
match some_tuple { 
    (a, ..) => println!("{}", a), 
      _ =>() 
} 

Więc moje pytanie:

  1. Czy to możliwe, przy użyciu funkcji bibliotecznych, konwertować tuple do łańcucha, określając dowolny separator?
  2. Jak napisać makro, aby móc odwzorować funkcje na krotki o dowolnych rozmiarach?
+1

Należy zauważyć, że w Rust arity krotki jest znany w czasie kompilacji (w przeciwieństwie do Pythona), a Rust nie ma jeszcze * parametrów variadycznych *; krotki są kompilowane przez kompilator i cechy są zaimplementowane dla wielu arach "ręcznie". –

+1

Python ma tendencję do typowania glom razem, gdy Rust ma przeciwną tendencję; w Pythonie wszystkie krotki są tego samego typu i wszystkie funkcje tego samego typu; w Rust, każda kombinacja typów pól w krotce jest innego typu, a każda funkcja jest unikalnym typem. Jest to różnica w podejściu: w Pythonie wszystko jest rozwiązywane w czasie wykonywania; w Rust, w czasie kompilacji. Krotki są w Rust po prostu nienazwanymi strukturami krotek, bez żadnego związku ze sobą. –

+0

@MatthieuM .: Czy możliwe byłoby uzyskanie arytmu krotki jako stałej? – oleid

Odpowiedz

14

Oto nadmiernie mądry makro rozwiązanie:

trait JoinTuple { 
    fn join_tuple(&self, sep: &str) -> String; 
} 

// FIXME(#19630) Remove this work-around 
macro_rules! e { 
    ($e:expr) => { $e } 
} 

macro_rules! tuple_impls { 
    () => {}; 

    (($idx:tt => $typ:ident), $(($nidx:tt => $ntyp:ident),)*) => { 
     impl<$typ, $($ntyp),*> JoinTuple for ($typ, $($ntyp),*) 
      where $typ: ::std::fmt::Display, 
        $($ntyp: ::std::fmt::Display),* 
     { 
      fn join_tuple(&self, sep: &str) -> String { 
       let parts: &[&::std::fmt::Display] = e!(&[&self.$idx, $(&self.$nidx),*]); 
       parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().connect(sep) 
      } 
     } 

     tuple_impls!($(($nidx => $ntyp),)*); 
    }; 
} 

tuple_impls!(
    (9 => J), 
    (8 => I), 
    (7 => H), 
    (6 => G), 
    (5 => F), 
    (4 => E), 
    (3 => D), 
    (2 => C), 
    (1 => B), 
    (0 => A), 
); 

fn main() { 
    let a = (1.3, 1, 'c'); 

    let s = a.join_tuple(", "); 
    println!("{}", s); 
    assert_eq!("1.3, 1, c", s); 
} 

Podstawowym założeniem jest to, że możemy wziąć krotki i rozpakować go do &[&fmt::Display]. Kiedy już to zrobimy, możemy bezpośrednio zamapować każdy element na ciąg znaków, a następnie połączyć je wszystkie za pomocą separatora. Oto, co to będzie wyglądać na własną rękę:

fn main() { 
    let tup = (1.3, 1, 'c'); 

    let slice: &[&::std::fmt::Display] = &[&tup.0, &tup.1, &tup.2]; 
    let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect(); 
    let joined = parts.connect(", "); 

    println!("{}", joined); 
} 

Następnym krokiem byłoby stworzenie cechę i wdrożyć go w konkretnym przypadku:

trait TupleJoin { 
    fn tuple_join(&self, sep: &str) -> String; 
} 

impl<A, B, C> TupleJoin for (A, B, C) 
    where A: ::std::fmt::Display, 
      B: ::std::fmt::Display, 
      C: ::std::fmt::Display, 
{ 
    fn tuple_join(&self, sep: &str) -> String { 
     let slice: &[&::std::fmt::Display] = &[&self.0, &self.1, &self.2]; 
     let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect(); 
     parts.connect(sep) 
    } 
} 

fn main() { 
    let tup = (1.3, 1, 'c'); 

    println!("{}", tup.tuple_join(", ")); 
} 

ten realizuje jedynie naszą cechę dla określonej wielkości krotki, która może być w porządku w niektórych przypadkach, ale na pewno nie jest jeszcze dostępna fajna. Model standard library wykorzystuje niektóre makra, aby zmniejszyć uciążliwość kopiowania i wklejania, które trzeba wykonać, aby uzyskać więcej rozmiarów. Zdecydowałem się na jeszcze lżejszą i zmniejszyć kopiowanie i wklejanie tego rozwiązania!

Zamiast wyraźnie i jawnie wymieniać każdy rozmiar krotki i odpowiadającą jej nazwę indeksu/nazwy, zrobiłem moje makro rekursywne. W ten sposób muszę go tylko raz wyliczyć, a wszystkie mniejsze rozmiary są tylko częścią połączenia rekursywnego. Niestety, nie mogłem wymyślić, jak zrobić to w kierunku do przodu, więc po prostu odwróciłem wszystko i cofnąłem się. Oznacza to niewielką nieefektywność, ponieważ musimy używać odwrotnego iteratora, ale ogólnie rzecz biorąc powinna to być niewielka cena do zapłacenia.

2

Bardzo pomógł mi other answer, ponieważ wyraźnie pokazał moc prostego systemu makro Rusta po użyciu rekursji i dopasowywania wzorców.

Udało mi się zrobić kilka prostych poprawek (może być w stanie uczynić wzorce nieco prostszymi, ale jest to raczej trudne) na wierzchu, aby lista krotek accessor-> typ była odwrócona przez makro w kompilacji przed ekspansją na realizację cecha tak, że już nie trzeba mieć .rev() połączenia w czasie wykonywania, dzięki czemu jest bardziej wydajny:

trait JoinTuple { 
    fn join_tuple(&self, sep: &str) -> String; 
} 

macro_rules! tuple_impls { 
    () => {}; // no more 

    (($idx:tt => $typ:ident), $(($nidx:tt => $ntyp:ident),)*) => { 
     /* 
     * Invoke recursive reversal of list that ends in the macro expansion implementation 
     * of the reversed list 
     */ 
     tuple_impls!([($idx, $typ);] $(($nidx => $ntyp),)*); 
     tuple_impls!($(($nidx => $ntyp),)*); // invoke macro on tail 
    }; 

    /* 
    * ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse 
    + is empty (see next pattern) 
    */ 
    ([$(($accIdx: tt, $accTyp: ident);)+] ($idx:tt => $typ:ident), $(($nidx:tt => $ntyp:ident),)*) => { 
     tuple_impls!([($idx, $typ); $(($accIdx, $accTyp);)*] $(($nidx => $ntyp),) *); 
    }; 

    // Finally expand into the implementation 
    ([($idx:tt, $typ:ident); $(($nidx:tt, $ntyp:ident);)*]) => { 
     impl<$typ, $($ntyp),*> JoinTuple for ($typ, $($ntyp),*) 
      where $typ: ::std::fmt::Display, 
        $($ntyp: ::std::fmt::Display),* 
     { 
      fn join_tuple(&self, sep: &str) -> String { 
       let parts = vec![self.$idx.to_string(), $(self.$nidx.to_string()),*]; 
       parts.join(sep) 
      } 
     } 
    } 
} 

tuple_impls!(
    (9 => J), 
    (8 => I), 
    (7 => H), 
    (6 => G), 
    (5 => F), 
    (4 => E), 
    (3 => D), 
    (2 => C), 
    (1 => B), 
    (0 => A), 
); 

#[test] 
fn test_join_tuple() { 
    let a = (1.3, 1, 'c'); 

    let s = a.join_tuple(", "); 
    println!("{}", s); 
    assert_eq!("1.3, 1, c", s); 
}