いいえ、モジュールを複数のファイルに分割することはできません。また、異なる場所で関数を定義することもできません。これに最も近いのは、さまざまなモジュールで定義されたインスタンスを持つ型クラスの一部である関数です。しかし、それはおそらくあなたがここで望んでいるものではありません.
ただし、相互に再帰的なモジュールをコンパイルすることは可能です。理論的には、これは Just Work (tm) のはずですが、GHC ではそれを行うためにちょっとしたフープ ジャンプが必要です。詳細については、ユーザーズ ガイドを参照してください。周期的なモジュールのインポート エラーが発生した場合、これによりそのバージョンが機能するようになります。
網羅的なパターン マッチ エラーをキャッチして、別のことを試す "良い" 方法はありません。あまり良くない方法が複数ありますが、おそらくそこに行きたくないでしょう。
多数のケースを含む 1 つのデータ型でパターン マッチングを行うことが目標である場合、相互に再帰的なモジュールや型クラスを台無しにすることなく物事を分割する最も簡単な方法は、各コンストラクターの内容を取得する別のモジュールに別の関数を持たせることです。直接引数として、他のものをインポートしてディスパッチを行うモジュール内のすべてのケースで単一のパターンマッチ。
同様の名前のモジュールを持つ、Foo
ケースA
、、、 &c.を持つタイプがあるとします。B
「中央」モジュールでは、次のものを使用できます。
doStuff (A x y) = A.doStuffA x y
doStuff (B z) = B.doStuffB z
...等々。
一部のシナリオでは、同様の方法でデータ型全体を分割し、コンストラクターごとに個別の型を作成することも理にかなっていますdata Foo = A FooA | B FooB | ...
。これは、複数の方法で相互に再帰的になるデータ型が複雑に絡み合っている場合に最も役立ちます。典型的な例は AST です。
さて、大ざっぱなことをせずに、必要なものをシミュレートする 1 つの方法を次に示します。
まず、理想的な方法で関数をさまざまなモジュールに分割します。次に、次の変更を行います。
evalExp
中央のモジュールから、ケースを含む各モジュールをインポートします。あいまいさを避けるために、必要に応じて修飾されたインポートを使用してください。各 eval 関数のリストを定義し (それらはすべて同じ型である必要があります)、「実数」evalExp
を次のように定義します。
expCases = [A.GHC.Num.evalExp, A.GHC.Types.evalExpr {- etc... -} ]
evalExpCases exp = mapMaybe (\eval -> eval evalExp exp) expCases
evalExp exp = case evalExpCases exp of
(r:_) -> -- use the first result
[] -> -- no cases matched
基本的に、これは、Maybe
網羅的でないパターンを明示的に示すために使用し、直接再帰をfix
、結合された再帰関数が (個別に非再帰的な) ケースの各セットに渡されるスタイルの構造に置き換えます。
かなり厄介ですが、本当に良い方法があるかどうかはわかりません。Template Haskell を使用してすべてのがらくたを自動化する方法があるかもしれませんが、それはおそらく手動で行うのと同じくらい面倒です。
個人的には、おそらく歯を食いしばって、すべてを 1 つのモジュールに残すでしょう。