2016年5月10日 星期二

使用Rust generic 實作九九乘法表

之前看到一些瘋狂的程式設計師,用C++的template實作九九乘法表,大體的概念就是利用編譯器展開template ,並設定特化的template 作為終止條件,像這個:
用 C++ template 實作九九乘法表

我們可以用 Rust 的generic type 做到類似的事情,概念是一樣的
試過用Rust 的Macro 來寫,這樣會遇到問題,如下如果我們接到一個table,對它展開:
macro_rules! node {
  ( $x:expr, $y:expr ) =>; {
    println!("{}*{}={}", $x, $y, $x*$y);
  }
}

這樣一個node 是沒問題的,但在進行row 展開時,macro 並不像template 一樣會對特化優先匹配,因此
macro_rules! row {
  ( $x:expr, 1) =>; {
    node!($x, $y)
  };
  ( $x:expr, $y:expr ) =>; {
    row!($x, $y-1)
    node!($x, $y)
  };
}
是沒有用的,編譯時仍會在展開 row! 那行發出警告recursion limit reached。

用generic 的function 來寫,其實就一般般,我們定義Node, Row, Table function,使用型別T 作為generic型別,Table 在row 不為一的時候會特化為row 少一的 Table 和Row;Row 在col 不為一的時候會特化為col 少一的Row 和Node,反之則只特化為Row 跟Node。
為了特化時的運算,我們對型別 T至少要有下面那一大排的要求:

  • Copy: 可複製,否則類似下面的row 被傳入Row 跟Node function 中,use moved value
  • PartialOrd: 可比較大小,用在 col > T::one()
  • One: T 有定義 one(),對大多數的Rust primitive type,都有定義Zero 跟One 兩種屬性,可以用在Add 跟 Mul,即Add T::zero() 其值不變,Mul T::one() 其值不變,不知道為啥Rust 核心對這部分好像會大改,用了它們也要在程式加上#![feature(zero_one)]才會動
  • Sub<T, Output=T>: T可以減T 得到一個T,為了下面的 col-T::one(),
  • Mul<T, Output=T>: T可以乘T 得到一個T,為了最裡面的 row * col = (row * col)
完整的Row 程式為:
fn Row(row: T, col: T) {
  where T: Copy+PartialOrd+One+Sub+Mul+Debug {
  if col > T::one() {
    Row(row, col-T::one());
  }
  Node(row, col);
  print!("\t");
}
Table function 類似,只是判斷對象變成row;Node function 則是印出結果。主程式就呼叫 Table(9u32, 9u32) 就能看見漂亮的九九乘法表了。

怎麼說呢,感覺跟C++ template 的寫法還是有點不同的感覺呢…

沒有留言:

張貼留言