2020/06/30

RustでOCamlのL99(Logic and Codes)をやってみる

Rustのenumが活躍しそう

ここ最近ずっとOCamlのL99をCommon Lispで書いてみるということをやっているのですが、あまり同じ言語ばかりやっていては、私の頭の中から他言語の知識が消えてしまう恐れがありますので、定期的に使う必要があります。悲しい脳みそです。

Common Lispのほうは現在L-37あたりまで進みましたが、今回は気分転換に、その先のL-46あたりのLogic and CodesというパートをRustでやってみようと思います。

まずドキュメントには以下のコードが書かれています。


# type bool_expr =
    | Var of string
    | Not of bool_expr
    | And of bool_expr * bool_expr
    | Or of bool_expr * bool_expr;;

これはOCamlのヴァリアント型で再帰になっているので、再帰ヴァリアントとも言うらしい。

Rustのenumも同様のことができるので、まずここに関しては書きやすかったです。


use self::BoolExpr::*;

#[derive(Debug)]
enum BoolExpr {
    Var(String),
    Not(Box<BoolExpr>),
    And(Box<BoolExpr>, Box<BoolExpr>),
    Or(Box<BoolExpr>, Box<BoolExpr>),
}

1行目のuse self::BoolExpr::*;は実際使う際に、毎回BooExpr::....と書くのが面倒なので、このように書いています。

これをドキュメントに沿って、実際使ってみます。


use self::BoolExpr::*;

#[derive(Debug)]
enum BoolExpr {
    Var(String),
    Not(Box<BoolExpr>),
    And(Box<BoolExpr>, Box<BoolExpr>),
    Or(Box<BoolExpr>, Box<BoolExpr>),
}

fn main() {
    let expr = And(
        Box::new(Or(
                Box::new(Var("a".to_string())),
                Box::new(Var("b".to_string()))
                )),
        Box::new(And(
                Box::new(Var("a".to_string())),
                Box::new(Var("b".to_string()))
                )),
        );
    println!("{:?}", expr);
}


-------
出力結果
-------
And(Or(Var("a"), Var("b")), And(Var("a"), Var("b")))

結果はOCamlと同じになりました。

次にL-46,47のTruth tables for logical expressionsというのを書いてみます。Rustではimplを使ってenumにも関数を持たせることができるので、以下のような形になりました。


use self::BoolExpr::*;


#[derive(Debug)]
enum BoolExpr {
    Var(String),
    Not(Box<BoolExpr>),
    And(Box<BoolExpr>, Box<BoolExpr>),
    Or(Box<BoolExpr>, Box<BoolExpr>),
}

impl BoolExpr {
    fn eval<'a>(&self, a: &'a str, val_a: bool, b: &'a str, val_b: bool) -> bool {
        match self {
            Self::Var(s) => {
                if &s == &a {
                    val_a
                } else if &s == &b {
                    val_b
                } else {
                    panic!("The expression contains an invalid variable.");
                }
            },
            Self::Not(e) => !(e.eval(a, val_a, b, val_b)),
            Self::And(e1, e2) => {
                e1.eval(a, val_a, b, val_b) && e2.eval(a, val_a, b, val_b)
            },
            Self::Or(e1, e2) => {
                e1.eval(a, val_a, b, val_b) || e2.eval(a, val_a, b, val_b)
            }
        }
    }
    fn table<'a>(&self, a: &'a str, b: &'a str) -> Vec<(bool, bool, bool)> {
        vec![
            (true, true, self.eval(a, true, b, true)),
            (true, false, self.eval(a, true, b, false)),
            (false, true, self.eval(a, false, b, true)),
            (false, false, self.eval(a, false, b, false))
        ]
    }
}

fn main() {
    let expr = And(
        Box::new(Var("a".to_string())),
        Box::new(Or(
                Box::new(Var("a".to_string())),
                Box::new(Var("b".to_string()))
                )),
        );
    println!("{:?}", expr.table("a", "b"));
}

-------
出力結果
-------
[(true, true, true), (true, false, true), (false, true, false), (false, false, false)]

結果はOCamlと同じ感じです。

まとめ

OCaml -> Rustはやはり何となく書きやすく感じます。もちろんもうちょっと進めると苦しくなる場面が出てくる予感はしていますが、現在のところ…ということで。

Common Lispで書く際には、このパートをどう書くべきなのか?今後えげつない壁にぶち当たりそうです。