RustのマクロでPAIPの2章 A Rule-Based Solutionをやってみた

古き良きAIを勉強中です。

PAIPの2章で紹介されている簡単なルールで英文を作成するプログラムをRustで書きました。
https://github.com/norvig/paip-lisp/blob/master/docs/chapter2.md#23-a-rule-based-solution

グローバル変数の初期化と、それにアクセスする関数定義まで、Rustのマクロを使って書いたのでまとめました。

## A Rule-Based Solution
プログラムの内容。

```common-lisp

(defparameter *simple-grammar*
  '((sentence -> (noun-phrase verb-phrase))
    (noun-phrase -> (Article Noun))
    (verb-phrase -> (Verb noun-phrase))
    (Article -> the a)
    (Noun -> man ball woman table)
    (Verb -> hit took saw liked))
  "A grammar for a trivial subset of English.")
```

このように簡単な英語の文法をListで表現し、そこから英文を生成します。

Listの中の -> はみやすくしているだけで意味はありません。->の左が名刺や動詞といったルールの名前を示しています。->の先に単語（シンボル）が並んでいる場合はそこから一つ選びます。Articleならtheかaのどちらかがランダムで選択されます。->の右がListの場合は、それらをつなげて英文にしていきます。1991年に書かれたAIの本の最初の最初の例題なので、アルゴリズムよりもこういう形で書くとCommon Lispで上手く扱える事に注目したいところです。

## Rustの実装

ルールの部分をマクロを使ってそれっぽくしました。いくつかの単語から選ぶルールです。

```lisp
(Article -> the a)
(Noun -> man ball woman table)
(Verb -> hit took saw liked))
```

これがこう書けます。

```rust
simple_rule!(article, ->, "a the");
simple_rule!(noun,    ->, "man ball woman table");
simple_rule!(verb,    ->, "hit took saw liked");
```


### lazy_static!
(Article -> the a)を、["a", "the"] のように文字列の配列にして、グローバル変数にしました。一回だけ初期化するグローバル変数を作るのにlazy_static!が使えました。初期化したいグローバル変数をlazy_static!{}で囲んで、初期化ルーチンを書きます。gensymは次に説明します。
 
```rust
lazy_static! {
            static ref $gensym:Vec<&'static str> = {
                let mut table:Vec<&'static str> = Vec::new();
                for s in $e3.split(' ') {
                    table.push(s);
                }
                table
            };
        }
```

### gensym!
ルールが2つ以上になると、上で作ろうとしているグローバル変数名が被るので、gynsym!を使って名前がぶつからないようにしています。

gensym!は、このようにマクロ自体を gensym::gensym! {} でくくると、マクロの中身に$gensymとして値を渡せるようです。


```rust
macro_rules! simple_rule {
    ($e1: ident, $e11:tt, $e2: expr) => {
        gensym::gensym! {
          _simple_rule!{ $e1, $e11, $e2}
        }
    };
}
```

マクロ本体(\_simple_rule!)では、$gensymとして、変数名にアクセスできました。

```rust
fn $e1()-> Vec<&'static str>  {
     vec![$gensym[random_elm_idx($gensym.len())]]
}
```


https://docs.rs/gensym/0.1.0/gensym/
gensymの情報はこれくらいしか見つからず、他に注意点がないのか気になります。

### 不要な引数 ->

見やすいために入っているだけのシンボル->については、マクロの引数の型にttを指定するとエラーなく処理できました。

```rust
 ($gensym:ident, $e1: ident, $e2:tt, $e3: expr) => {
```

だいだいこれだけやれば、後は普通のマクロを書けばOKです。

## 出来上がったルール
結局上のListで書かれたルールを、こう書くことができました。

```rust
composite_rule!(noun_phrase, ->, article, noun);
composite_rule!(verb_phrase, ->, verb, noun_phrase);
composite_rule!(sentence,    ->, noun_phrase, verb_phrase);
simple_rule!(article, ->, "a the");
simple_rule!(noun,    ->, "man ball woman table");
simple_rule!(verb,    ->, "hit took saw liked");
```

これで、関数を6つ、グローバル変数を3つ作っています。Rustのマクロすごい。

実行すると、頭悪そうな英文を無限に作ってくれます。

```
a table took the table .
a table liked a ball .
a table saw a man .
the table took a man .
a ball liked a table .
a man liked a woman .
a table hit a ball .
the man liked a man .
the table hit the man .
a woman liked the ball .
```


## ソース
以下、全ソースです。

```rust
//https://github.com/norvig/paip-lisp/blob/master/docs/chapter2.md#23-a-rule-based-solution
use rand::Rng;

#[macro_use]
extern crate lazy_static;
extern crate gensym;

fn random_elm_idx(i:usize) -> usize {
    let mut rng =  rand::thread_rng();
    rng.gen_range(0, i as u32) as usize
}

macro_rules! _simple_rule {
    ($gensym:ident, $e1: ident, $e2:tt, $e3: expr) => {
        lazy_static! {
            static ref $gensym:Vec<&'static str> = {
                let mut table:Vec<&'static str> = Vec::new();
                for s in $e3.split(' ') {
                    table.push(s);
                }
                table
            };
        }

        fn $e1()-> Vec<&'static str>  {
            vec![$gensym[random_elm_idx($gensym.len())]]
        }
    }
}

macro_rules! simple_rule {
    ($e1: ident, $e11:tt, $e2: expr) => {
        gensym::gensym! {
          _simple_rule!{ $e1, $e11, $e2}
        }
    };
}

macro_rules! composite_rule {
    ($e1: ident, $e2:tt, $e3: expr, $e4: expr) => {
        fn $e1() -> Vec<&'static str> {
            let mut n:Vec<&str> = Vec::new();
            for s in $e3() {
                n.push(s);
            }
            for s in $e4() {
                n.push(s);
            }
            n
        }
    };
}


/*
(defparameter *simple-grammer*
  '((sentence -> (noun-phrase verb-phrase))
    (noun-phrase -> (Article Noun))
    (verb-phrase -> (Verb noun-phrase))
    (Article -> the a)
    (Noun -> man ball woman table)
    (Verb -> hit took saw liked))
  "A grammar for a trivial subset of English")
*/

composite_rule!(noun_phrase, ->, article, noun);
composite_rule!(verb_phrase, ->, verb, noun_phrase);
composite_rule!(sentence,    ->, noun_phrase, verb_phrase);
simple_rule!(article, ->, "a the");
simple_rule!(noun,    ->, "man ball woman table");
simple_rule!(verb,    ->, "hit took saw liked");


fn conv_str(v:Vec<&'static str>) -> String {
    let mut cs:String = "".to_string();
    for s in v {
        cs = cs + s + " ";
    }
    cs + "."
}

fn main() {
    for _ in 0..10  {
        println!("{}", conv_str(sentence()));
    }
}
```