F# - 访问来自不同项目的引用单元格
F# - Accessing Reference Cells from Different Projects
我正在使用 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
...效果很好,我得到了成功的结果。
但是,当我尝试 运行 完全相同的测试函数时,这次来自另一个项目(称为 Interpreter),该项目引用了其中定义了 pstatement
的项目(称为 Parser,删除后来自 Program.fs in Parser 的主要函数),我收到以下错误:
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 项目中删除。
是否存在我不知道的参考单元限制导致了这个问题?由于在定义引用单元格的文件中一切正常,因此可以肯定地得出结论,问题出在从定义引用单元格的文件或项目之外的文件或项目中访问它的值。
编辑:
这是一个更完整的设置示例
Parser/Library.fs
namespace PCParser
module Parser =
let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()
let pread = ...
let pdisplay = ...
...
do pstatementref := choice [
pread
pdisplay
pset
pcompute
pif
pwhile
]
Interpreter/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
我猜这是因为您引用的项目(它定义了解析器)被编译为可执行文件而不是库。
这是一个表现出类似行为的最小示例。假设我们 project1
具有以下内容:
module Xyz
let r = ref None
do r := Some 42
我们有 project2
引用 project1
并具有以下代码:
printfn "GOT: %A" Xyz.r
如果将 project1
编译为可执行文件,运行 代码将打印 GOT: <null>
,但如果将 project1
的输出类型更改为 class library (dll) 然后这段代码将打印 GOT: { contents=Some 42 }
.
原因是 F# 编译器为 class 库和可执行文件编译初始化代码的方式不同。对于可执行文件,它假定可执行文件将被执行,因此它在 main
函数中运行初始化代码。对于库,它不能假设,因此它在静态构造函数中进行检查。
要解决此问题,您需要将代码编译为 class 库,或者您可以添加一些自定义初始化函数,测试项目可以在 运行 测试之前调用(并且可以从 main
)
调用
我正在使用 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
...效果很好,我得到了成功的结果。
但是,当我尝试 运行 完全相同的测试函数时,这次来自另一个项目(称为 Interpreter),该项目引用了其中定义了 pstatement
的项目(称为 Parser,删除后来自 Program.fs in Parser 的主要函数),我收到以下错误:
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 项目中删除。
是否存在我不知道的参考单元限制导致了这个问题?由于在定义引用单元格的文件中一切正常,因此可以肯定地得出结论,问题出在从定义引用单元格的文件或项目之外的文件或项目中访问它的值。
编辑:
这是一个更完整的设置示例
Parser/Library.fs
namespace PCParser
module Parser =
let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()
let pread = ...
let pdisplay = ...
...
do pstatementref := choice [
pread
pdisplay
pset
pcompute
pif
pwhile
]
Interpreter/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
我猜这是因为您引用的项目(它定义了解析器)被编译为可执行文件而不是库。
这是一个表现出类似行为的最小示例。假设我们 project1
具有以下内容:
module Xyz
let r = ref None
do r := Some 42
我们有 project2
引用 project1
并具有以下代码:
printfn "GOT: %A" Xyz.r
如果将 project1
编译为可执行文件,运行 代码将打印 GOT: <null>
,但如果将 project1
的输出类型更改为 class library (dll) 然后这段代码将打印 GOT: { contents=Some 42 }
.
原因是 F# 编译器为 class 库和可执行文件编译初始化代码的方式不同。对于可执行文件,它假定可执行文件将被执行,因此它在 main
函数中运行初始化代码。对于库,它不能假设,因此它在静态构造函数中进行检查。
要解决此问题,您需要将代码编译为 class 库,或者您可以添加一些自定义初始化函数,测试项目可以在 运行 测试之前调用(并且可以从 main
)