【问题标题】:Is it possible to define types that depend on each other and are defined in separated files?是否可以定义相互依赖并在单独的文件中定义的类型?
【发布时间】:2010-10-21 00:56:27
【问题描述】:

我正在尝试实现一个具有扩展解析功能的库。我决定使用 fsyacc,因为我从大学就知道它。不幸的是我遇到了以下问题。

我为我的语法头(Head)定义了一个类,并将其实现放在一个文件中。然后我将解析器定义为:

...
%start head
%type <Head> head
...

Fsyacc 生成分离模块(Parser)。为了成功,它必须按以下顺序编译:Head.fsParser.fs

为了使这个库类似于您在 .NET 中可以找到的库,我想向 Head 添加一个静态 Parse 方法。不幸的是,我需要使用 Parser 模块中的方法。

我知道这种类型依赖可以用'and'操作符解决,但它只适用于一个文件中定义的类型。

是否有任何其他方法可以创建相互依赖的类型,即使它们位于单独的文件中? 我一直在寻找像 C/C++ 中的声明/实现分离机制,但我不能什么都找不到。

【问题讨论】:

    标签: .net f# mutual-recursion fsyacc


    【解决方案1】:

    简短的回答:不。在 F# 2.0 中,无法跨多个文件执行相互递归的实体。 (这是我们计划在下一版本的语言中解决的问题。)

    您可以通过多种方式解决此问题,通常使用间接点和突变。例如,您的 Head 类型可能有一个静态的“InitializeParser”方法,该方法将函数值插入可变全局变量,然后 Head 中定义的静态 Parse 方法可以通过该可变全局调用,并且在实际定义解析器之后,它可以去调用 InitializeParser 来插入值。(如果这没有意义,我可以更详细地拼写出来。)

    【讨论】:

    • 我很好奇:您是否还计划总体上放松线性依赖关系,以便文件和函数的顺序无关紧要?
    • 不,不,不,不,不。另外,没有。
    • Brian 这是否被添加到 F# 3.0 中?
    【解决方案2】:

    我希望这是可能的。在我阅读了布赖恩的回复后,我开始寻找合适的解决方法。我不想强迫图书馆用户调用任何初始化方法。因此,我想出了一些不同的东西。

    如果编译器无法在编译时解决依赖关系,我可以在运行时自行解决。 这是我的 DepenendciesResolver 的定义

    module DependenciesResolver = 
        let GetMethod(_type, _method) =
            lazy (
                let a = System.Reflection.Assembly.GetExecutingAssembly()
                let t = a.GetType(_type)
                t.GetMethod(_method)
                )
    

    以及在单独文件中定义的类示例:

    A.fs

    namespace MutualRecursion
    type A() =
        static member _b = DependenciesResolver.GetMethod("MutualRecursion.B", "b")
        static member b() = A._b.Value.Invoke(null, [||])
    

    B.fs

    nameespace MutualRecursion
    type B =
        static member b() = 
            printf "Called b()"
    

    编译顺序为:A.fs B.fs

    【讨论】:

      【解决方案3】:

      您不能使用在这两个文件之后编译并使用新方法扩展 Head 的第三个文件来解决这个问题吗?

      【讨论】:

      • 这适用于 Head,因为它之后没有继承任何类,但我还有一个带有 ExpressionBase 的层次结构。我也想为这个类添加一个 Parse() 方法。当然需要在 Parser 之前编译完整的层次结构。然后,如果创建一个继承 ExpressionBase 的类,则无法使其成为已定义类的超类型。
      • 不过,你也许可以做一个扩展方法。
      【解决方案4】:

      我会做类似以下的事情(我怀疑这大致是布赖恩的提议)。请注意,用户不必进行任何棘手的初始化 - 类型本身知道如何“打结”。

      Head.fs

      type IParser =
        abstract Parse : string -> int // or whatever
        ...
      
      type Head() =
        static let mutable parser = Unchecked.defaultof<IParser>
        static member internal SetParser p = parser <- p
        member x.DoSomethingCool() =
          let i = parser.Parse("something to parse")
          ...
      

      Parser.fs

      type Parser private () =
        static do
          Head.SetParser (Parser())
        interface IParser with
          member x.Parse s = 0
          ...
      

      【讨论】:

      • 你试过编译和运行你的代码吗?我从let i = parser.Parse("something to parse") 行得到了 NullReferenceException 。问题在于CLR。根据'CLR via C# (Dev-Pro)',用于设置解析器的类型构造函数在创建此类型的第一个实例的代码之前调用,或者在访问非继承字段或类成员的代码之前调用。因此,如果你想强制 CLR 调用 Parser 类型构造函数,你需要访问它的任何成员——回到绘图板。
      猜你喜欢
      • 2013-09-03
      • 2011-09-27
      • 2022-12-12
      • 1970-01-01
      • 1970-01-01
      • 2014-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多