Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# interactive - how to use precompiler directives when multiple files reference the same assembly?

So, in Project AB I have FileA.fs and FileB.fs. FileB uses definitions from FileA, and both FileA and FileB use definitions from Project C (written in C#).

In FileA.FS, I have:

#if COMPILED
namespace ProjectAB
#else
#I "bin\debug"
#r "ProjectZ.dll"
#endif

...which works how it's supposed to -- I can run the whole file in F#-Interactive and it's great.

In FileB.fs, my header is:

#if COMPILED
module ProjectAB.ModuleB
#else
#load "FileA.fs"
#I "bin\debug"
#r "ProjectZ.dll"
#endif

But when I run this (from FileB), I get the error:

FileA.fs(6,1): error FS0222: Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration.

According to the fsi.exe reference, the #load directive "Reads a source file, compiles it, and runs it". But it seems like it must be doing so without the COMPILED directive defined, because it doesn't see the "namespace ProjectAB" declaration.

How can I set up my headers so that I can run either file in F#-interactive?

Edit Per latkin's answer below, I created a script as the last file in the project, _TestScript.fsx. I removed all the precompiler stuff from the other files and set this as the header of the .fsx file:

#if INTERACTIVE
#I "bin\debug"
#r "ProjectZ.dll"
#load "FileA.fs"
#load "FileB.fs"
#endif

When I run this in the interactive, it correctly loads ProjectZ, FileA, and FileB for me to access in the interactive window.

However, in _TestScript.fsx, I get squiggly red lines and no intellisense on any of the functions/types from the referenced files (including the "open" statements).

Is there something else I need to set up in the script file to make the intellisense work? (The answer might be pretty basic since I have not used .fsx files before.)

like image 313
Overlord Zurg Avatar asked Oct 31 '22 09:10

Overlord Zurg


1 Answers

I don't think there is a way to do this smoothly. A few things to consider:

  • INTERACTIVE is always defined when you are being processed by fsi.exe, whether you are a .fsx, .fs, #load'ed, whatever. COMPILED is similarly always defined when you are being processed by fsc.exe. I can see how the quoted phrase from the docs maybe doesn't make this totally crystal clear.

  • You can only declare namespaces in fsi from a #load'ed file

So if you want your file to declare a namespace, and to work as the single file in interactive, then the namespace has to be #ifdef'ed out. But that also means the namespace will be #ifdef'ed out when the file is #load'ed...

You might be able to work around this by conditionally declaring it as a module, not a namespace. Or perhaps creating additional, more granular defines. It will be tricky.

Trying to get source files to work properly as part of a compiled library and simultaneously as single-file scripts is not easy, and I don't think the tooling was designed with this scenario in mind. More common is to have all of your library files behave purely as library files, then use dedicated standalone scripts which #loads the .fs files they need. This keeps the driving code and the library code separate, and things fit together more cleanly.

like image 191
latkin Avatar answered Jan 04 '23 14:01

latkin