私は現在、OCamlを使用した小さなプロジェクトに取り組んでいます。単純な数式の単純化。私は式の中にあるパターンを見つけ、それらを単純化して、式の中の括弧の数を減らすことになっています。これまでのところ、2つを除くほとんどのルールを実装できました。そのため、再帰的なパターンマッチング「フィルター」関数を作成することにしました。実装する必要がある2つのルールは次のとおりです。
-a-(b + c)または類似の形式のすべての式をa-b-cに変換します
-a /(b * c)または類似の形式のすべての式をa/b/cに変換します
...これはかなり単純だと思います。1つを実装できれば、もう1つは簡単に実装できます。ただし、再帰的なパターンマッチング機能に問題があります。私のタイプ式はこれです:
type expr =
| Var of string (* variable *)
| Sum of expr * expr (* sum *)
| Diff of expr * expr (* difference *)
| Prod of expr * expr (* product *)
| Quot of expr * expr (* quotient *)
;;
そして、私が主に問題を抱えているのは、マッチ式です。たとえば、私はこのようなものを試しています:
let rec filter exp =
match exp with
| Var v -> Var v
| Sum(e1, e2) -> Sum(e1, e2)
| Prod(e1, e2) -> Prod(e1, e2)
| Diff(e1, e2) ->
match e2 with
| Sum(e3, e4) -> filter (diffRule e2)
| Diff(e3, e4) -> filter (diffRule e2)
| _ -> filter e2
| Quot(e1, e2) -> ***this line***
match e2 with
| Quot(e3, e4) -> filter (quotRule e2)
| Prod(e3, e4) -> filter (quotRule e2)
| _ -> filter e2
;;
ただし、マークされた行の一致式は、「主一致」ではなく、前の「内部一致」の一部として認識されているため、すべての「Quot(...)」式が認識されることはありません。このような他のマッチ式の中にマッチ式を含めることもできますか?そして、私が他の可能性と一致し続けることができるように、内部一致を終了する正しい方法は何でしょうか?
ロジックは無視してください。最初に思いついたのはほとんどのことです。この「一致」エラーを最初に処理する必要があるため、再試行を処理する方法に関する推奨事項や、論理は歓迎されます。
クイックソリューション
内側の一致の前後に括弧またはbegin
/end
を追加するだけです。
let filter exp = expを に一致させる|変数v->変数v |合計(e1、e2)->合計(e1、e2) |製品(e1、e2)->製品(e1、e2) |差分(e1、e2)-> (e2を と一致させる|合計(e3、e4)->フィルター(diffRule e2) |差分(e3、e4)->フィルター(diffRule e2) | _->フィルターe2) | Quot(e1、e2)-> (e2と一致する | Quot(e3、e4)->フィルター(quotRule e2) | Prod(e3、e4)->フィルター(quotRule e2) | _->フィルターe2) ;;
簡略化
特定のケースでは、ネストされた一致の必要はありません。より大きなパターンを使用できます。 "|
"( "or")パターンを使用して、ネストされたルールの重複を排除することもできます。
let rec filter exp =
match exp with
| Var v -> Var v
| Sum (e1, e2) -> Sum (e1, e2)
| Prod (e1, e2) -> Prod (e1, e2)
| Diff (e1, (Sum (e3, e4) | Diff (e3, e4) as e2)) -> filter (diffRule e2)
| Diff (e1, e2) -> filter e2
| Quot (e1, (Quot (e3, e4) | Prod (e3, e4) as e2)) -> filter (quotRule e2)
| Quot (e1, e2) -> filter e2
;;
未使用のパターン変数を_
(下線)に置き換えることで、さらに読みやすくすることができます。これは、(e3,e4)
タプルなどのサブパターン全体でも機能します。
let rec filter exp =
match exp with
| Var v -> Var v
| Sum (e1, e2) -> Sum (e1, e2)
| Prod (e1, e2) -> Prod (e1, e2)
| Diff (_, (Sum _ | Diff _ as e2)) -> filter (diffRule e2)
| Diff (_, e2) -> filter e2
| Quot (_, (Quot _ | Prod _ as e2)) -> filter (quotRule e2)
| Quot (_, e2) -> filter e2
;;
同様に、簡略化を進めることができます。たとえば、最初の3つのケース(Var
、Sum
、Prod
)は変更されずに返され、直接表現できます。
let rec filter exp =
match exp with
| Var _ | Sum _ | Prod _ as e -> e
| Diff (_, (Sum _ | Diff _ as e2)) -> filter (diffRule e2)
| Diff (_, e2) -> filter e2
| Quot (_, (Quot _ | Prod _ as e2)) -> filter (quotRule e2)
| Quot (_, e2) -> filter e2
;;
最後に、e2
をe
に置き換え、match
をfunction
ショートカットに置き換えることができます。
let rec filter = function
| Var _ | Sum _ | Prod _ as e -> e
| Diff (_, (Sum _ | Diff _ as e)) -> filter (diffRule e)
| Diff (_, e) -> filter e
| Quot (_, (Quot _ | Prod _ as e)) -> filter (quotRule e)
| Quot (_, e) -> filter e
;;
OCamlのパターン構文はいいですね。
Asやorパターンなどのアンダースコアを慎重に使用することで、これをより簡潔にすることができます(私はより明確に議論します)。結果として得られるコードは、割り当てが少ないため、より効率的です(Var、Sum、およびProdの場合)。
let rec filter = function
| Var _ | Sum _ | Prod _ as e -> e
| Diff (_, (Sum _ | Diff _) as e) -> filter (diffRule e)
| Diff (_,e) -> e
| Quot (_, (Quot _| Prod _) as e) -> filter (quoteRule e)
| Quot (_,e) -> filter e
;;