F # — доступ к ссылочным ячейкам из разных проектов

#functional-programming #f# #fparsec

#функциональное программирование #f# #fparsec

Вопрос:

Я пишу рекурсивный анализатор с помощью 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
  

… это означает, что ссылочной ячейке никогда не присваивается какое-либо значение, однако код, который это делает, не был удален из проекта синтаксического анализатора.

Существует ли ограничение ссылочных ячеек, о котором я не знаю, что вызывает эту проблему? Поскольку в файле, в котором определена ссылочная ячейка, все работает нормально, можно с уверенностью заключить, что проблема заключается в доступе к ее значению из файла или проекта за пределами того, в котором оно определено.

Редактировать:

Вот более полный пример того, как все настроено

Синтаксический анализатор / 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
    ]
  

Интерпретатор / 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
  

Комментарии:

1. Где именно находится вызов, который присваивает ссылку? Как вы это выполняете?

2. Вызов, который присваивает ссылку, находится в проекте синтаксического анализатора и модуле, который я open использую из проекта интерпретатора. Я предполагал, что назначение будет выполняться по умолчанию при открытии модуля.

3. Не могли бы вы опубликовать минимальное воспроизведение, пожалуйста? Из вашего объяснения неясно, что содержится / где вложено.

4. Я попытался уточнить в редактировании, как все настроено. Минимальный пример Томаса работает как минимальное воспроизведение моей проблемы, однако я все еще не уверен, как ее можно решить. Я только недавно изменил проект синтаксического анализатора на classlib, но проблема сохраняется.

Ответ №1:

Я предполагаю, что это связано с тем, что проект, на который вы ссылаетесь (который определяет анализатор), компилируется как исполняемый файл, а не как библиотека.

Вот минимальный пример, который демонстрирует аналогичное поведение. Скажем project1 , мы имеем следующее:

 module Xyz

let r = ref None
do r := Some 42
  

И у нас есть project2 эти ссылки project1 и имеет следующий код:

 printfn "GOT: %A" Xyz.r    
  

Если вы скомпилируете project1 как исполняемый файл, выполняемый код будет напечатан GOT: <null> , но если вы измените тип вывода project1 на библиотеку классов (dll), тогда этот код будет напечатан GOT: { contents=Some 42 } .

Причина в том, что компилятор F # по-разному компилирует код инициализации для библиотек классов и исполняемых файлов. Для исполняемых файлов предполагается, что исполняемый файл будет выполнен, и поэтому он запускает код инициализации в main функции. Для библиотек это не может предполагать, и поэтому он помещает проверки в статические конструкторы.

Чтобы исправить это, вам либо нужно скомпилировать свой код как библиотеку классов, либо вы можете добавить какую-либо пользовательскую функцию инициализации, которую тестовый проект может вызывать перед запуском тестов (и из которой можно вызывать main )

Комментарии:

1. Это полезная информация, и я определенно пропустил это. Как мы можем гарантировать, что проект скомпилирован как classlib, помимо его инициализации с dotnet new classlib -lang F# ... помощью? Я действительно пытался воссоздать проект синтаксического анализатора таким образом, но безуспешно, так что, возможно, я что-то упускаю.

2. Я думаю dotnet new , что генерирует .fsproj файл — в этом файле вы можете установить <OutputType> значение Exe (для исполняемого файла) или Library (для библиотеки)