3

私の現在のプロジェクトでは、40 の異なる型 (判別共用体) を持つ AST を使用しており、この AST のいくつかの型には循環依存があります。型はそれほど大きくないので、それらを 1 つのファイルにまとめtype ... and ...、相互に依存する型の構築を適用しました。

現在、AST の各要素の下でいくつかの計算を行う関数を追加しています。数行のコードを含む多くの関数があるため、ソース コードを読みやすくするために、これらの関数を別のファイルに分けました。

循環依存関係がない場合は問題ありません。依存関数が同じファイルにある場合にも機能します。この場合、let rec function1 ... and function2 ...構築を使用できます。

しかし、私の場合はうまくいきません。

また、署名ファイルがこれに役立つと誤って考えていましたが、その動作は C++ とは異なります。

  • 関数/型のアクセス モード (内部/パブリック) を定義するために使用されます。また、関数/型のコメント ヘッダーをここに追加することもできます。

私が考える唯一の解決策は、すべての関数を 1 つのファイルに移動し、let rec ... and ... and ... and ... and ...構築を使用することです。

誰かが異なる考えを持っている可能性がありますか?

4

1 に答える 1

6

コメントで述べたように、複数のファイル間で循環依存関係を持つ関数(またはタイプ)を分割する方法はありません。署名ファイルは主に文書化の目的で役立つため、役に立ちません。

依存関係が正確に何であるかを知らずにアドバイスを与えることは困難です。ただし、関数またはインターフェイスを使用して、実装の一部をリファクタリングできる場合があります。たとえば、次の場合:

let rec process1 (a:T1) = 
  match a with
  | Leaf -> 0
  | T2Thing(b) -> process2 b

and process2 (b:T2) = 
  match b with 
  | T1Thing(a) -> process1 a

関数を変更しprocess1て、2番目の関数を引数として取ることができます。これにより、相互再帰的ではなくなったため、実装を2つのファイルに分割できます。

// File1.fs
let process1 (a:T1) process2 = 
  match a with
  | Leaf -> 0
  | T2Thing(b) -> process2 b

// File2.fs
let rec process2 (b:T2) = 
  match b with 
  | T1Thing(a) -> process1 a process2

より明確な構造を見つけることができる場合(たとえば、論理的に関連する関数を含み、相互にアクセスする必要がある関数の2つのブロック)、インターフェースを定義することもできます。これは、2つの関数しかない例ではあまり意味がありませんが、次のようになります。

type IProcess2 = 
  abstract Process : T2 -> int

let process1 (a:T1) (process2:IProcess2) = 
  match a with
  | Leaf -> 0
  | T2Thing(b) -> process2.Process b

let rec process2 (b:T2) = 
  let process2i = 
    { new IProcess2 with 
        member x.Process(a) = process2 a }
  match b with 
  | T1Thing(a) -> 
    process1 a process2i

とにかく、これらはいくつかの一般的なテクニックです。作業しているタイプについて詳しく知らずに、より正確なアドバイスを提供することは困難です。詳細を共有できれば、再帰的な参照の一部を回避する方法を見つけることができるかもしれません。

于 2011-03-23T14:08:47.023 に答える