【问题标题】:F# - Accessing Reference Cells from Different ProjectsF# - 访问来自不同项目的参考单元格
【发布时间】:2021-01-08 06:03:27
【问题描述】:

我正在使用 FParsec 编写递归解析器,因此我正在使用 createParserForwardedToRef 创建一个虚拟解析器和引用单元,如下所示:

let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()

参考单元最终“分配”以下内容:

do pstatementref := choice [
    pread
    pdisplay
    pset
    pcompute
    pif
    pwhile
]

当我从创建引用单元格并使用以下代码分配上述值的文件中测试解析器 pstatement 时...

let test p str =
    match runParserOnFile p () str Encoding.ASCII with
    | Success(result, _, _)   -> printfn "Success: %A" result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

[<EntryPoint>]
let main argv =
    argv
    |> String.concat "+"
    |> test (manyTill pstatement (pword "HALT"))
    0

...效果很好,我得到了成功的结果。

但是,当我尝试运行完全相同的测试函数时,这次来自另一个项目(称为解释器),该项目引用了其中定义了 pstatement 的项目(称为解析器,在从 Program.fs 中删除主函数之后解析器),我收到以下错误:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at FParsec.Primitives.manyTill@890.Invoke(CharStream`1 stream)
   at FParsec.CharParsers.applyParser[Result,UserState](FSharpFunc`2 parser, CharStream`1 stream)
   at FParsec.CharParsers.runParserOnFile[a,u](FSharpFunc`2 parser, u ustate, String path, Encoding encoding)
   at Program.test[a](FSharpFunc`2 p, String str) in /home/rosalogia/Projects/FSharp/PCParser/src/Interpreter/Program.fs:line 16
   at Program.main(String[] argv) in /home/rosalogia/Projects/FSharp/PCParser/src/Interpreter/Program.fs:line 32

...这意味着永远不会为引用单元格分配任何值,但是这样做的代码尚未从 Parser 项目中删除。

我不知道导致此问题的参考单元是否存在限制?由于在定义参考单元格的文件中一切正常,因此可以安全地得出结论,问题在于从定义它的文件或项目之外的文件或项目访问其值。

编辑:

这里有一个更完整的例子来说明如何设置

解析器/库.fs

namespace PCParser

module Parser =
    let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()

    let pread = ...
    let pdisplay = ...

    ...

    do pstatementref := choice [
        pread
        pdisplay
        pset
        pcompute
        pif
        pwhile
    ]

解释器/Program.fs

open PCParser.Parser
open FParsec
open System.Text

let test p str =
    match runParserOnFile p () str Encoding.ASCII with
    | Success(result, _, _)   -> printfn "Success: %A" result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

[<EntryPoint>]
let main argv =
    argv
    |> String.concat "+"
    |> test (manyTill pstatement (pword "HALT"))
    0

【问题讨论】:

  • 分配引用的调用到底在哪里?你是怎么执行的?
  • 分配引用的调用在 Parser 项目和模块中,我 open 来自 Interpreter 项目。我的假设是在打开模块时默认会执行分配。
  • 你能发布一个最小的复制吗?从您的解释中不清楚什么包含/嵌套在哪里。
  • 我试图在编辑中澄清事情是如何设置的。 Tomas 的最小示例可以作为我问题的最小复制,但是我仍然不确定如何解决它。我最近才将 Parser 项目更改为 classlib,但问题仍然存在。

标签: functional-programming f# fparsec


【解决方案1】:

我的猜测是这是因为您引用的项目(定义解析器)被编译为可执行文件而不是库。

这是一个展示类似行为的最小示例。假设我们有 project1 和以下内容:

module Xyz

let r = ref None
do r := Some 42

我们有 project2 引用 project1 并具有以下代码:

printfn "GOT: %A" Xyz.r    

如果将project1 编译为可执行文件,运行代码将打印GOT: &lt;null&gt;,但如果将project1 的输出类型更改为类库(dll),则此代码将打印GOT: { contents=Some 42 }

原因是 F# 编译器为类库和可执行文件编译初始化代码的方式不同。对于可执行文件,它假定可执行文件将被执行,因此它在main 函数中运行初始化代码。对于库,它不能假设,因此它将检查放在静态构造函数中。

要解决此问题,您需要将代码编译为类库,或者您可以添加一些自定义初始化函数,测试项目可以在运行测试之前调用(可以从main 调用)

【讨论】:

  • 这是有用的信息,我肯定忽略了这一点。除了使用dotnet new classlib -lang F# ... 初始化项目之外,我们如何确保将项目编译为类库?我实际上已经尝试过以这种方式重新创建 Parser 项目,但没有运气,所以也许我遗漏了一些东西。
  • 我认为dotnet new 会生成一个.fsproj 文件——在这个文件中,您可以将&lt;OutputType&gt; 设置为Exe(用于可执行文件)或Library(用于库)
猜你喜欢
  • 2017-01-19
  • 1970-01-01
  • 2015-04-07
  • 2023-03-05
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多