Should it be possible to put the #load directive inside #if directive in F# fsx file? - f#

I have a fsx file where I try to do this
#if DEV
#load "MyFile.fs"
#endif
// Later in the file
#if DEV
callSomethingFromMyFile()
#endif
The callSomethingFromMyFile() works if I remove the #if DEV ... #endif around the #load directive.
I realize that this might be a weird thing, but it is because I'm using fable to compile to F# to js, and if I want to exclude a file when production "build" to reduce js file size.

In regular F# scripts it is possible, it seems like fable doesn't handle it.
To verify that it works in regular F# I created the following to files:
MyModule.fs:
module MyModule
type A = {b: string}
script.fsx:
#if DEV
#load "./MyModule.fs"
#endif
#if DEV
open MyModule
printfn "Hello A: %A" {b = "yolo"}
#endif
printfn "Done"
running fsharpi --define:DEV --exec script.fsx works as expected. I expect fsi on Windows to work as well.

Ive fixed this in the following PR: https://github.com/fable-compiler/Fable/pull/429
You can currently pass defines to the interactive checker its just that for fsx files this was not currently being done.

"
Some directives are available when you are executing scripts in F# Interactive that are not available when you are executing the compiler. The following table summarizes directives that are available when you are using F# Interactive.
"
https://learn.microsoft.com/en-us/dotnet/articles/fsharp/tutorials/fsharp-interactive/index#differences-between-the-interactive-scripting-and-compiled-environments
The table then lists #load amongst others. Not entirely clear that text (compiler vs. preprocessor), but it also kind of makes sense...

Related

Import Pcaml grammar to extend OCaml's printer using camlp5

I want to create a printer extension for OCaml using camlp5. My code would look like the example of this tutorial but instead of creating my own extension of the grammar, I would like to use OCaml's grammar to parse a program.
For that, I would like to use the Pcaml module to parse the given string with OCaml's grammar. Unfortunately, each time I try to use it, I get the:
Required module 'Pcaml' is unavailable
This is the part of my code where I load and open modules, as well as part of the code that uses Pcaml:
#load "pa_extprint.cmo";;
#load "q_MLast.cmo";;
#load "pa_o.cmo";;
open Pcaml;;
open Pprintf;;
let pa_ocaml = Grammar.Entry.create Pcaml.gram "pcaml_gram";;
I tried multiple command to run the program, like for example:
ocamlc -pp camlp5o -I +camlp5 gramlib.cma <my_file>.ml
What do I need to be able to use Pcaml and Pcaml.gram?
I recommend to use ocamlfind to build and link your programs. The only reason for newcomer against it, is that thing could become buggy when you use Windows without WSL. The compilation command without error is below
ocamlfind c -syntax camlp5o -package camlp5 -linkpkg a.ml
#load "pa_extprint.cmo";;
#load "q_MLast.cmo";;
#load "pa_o.cmo";;
open Pcaml;;
open Pprintf;;
let pa_ocaml : int Grammar.Entry.e = Grammar.Entry.create Pcaml.gram "pcaml_gram";;
FYI, your #load commands can and should be replaced by specifying right ocamlfind's packages.

Documentation/List of standard preprocessing symbols

As an example, I'm using the following preprocessing directive
#if COMPILED
let context = Sql.GetDataContext(ConfigurationManager.ConnectionStrings.[AppDB].ConnectionString)
#else
let context = Sql.GetDataContext()
#endif
so that I'm able to test a dll library from F# interactive, to give you an idea
#I #"bin\Debug"
#r #"import.dll"
#r #"FSharp.Data.SqlProvider.dll"
#load "Library1.fs"
open SqlLib
open SqlDB
// Define your library scripting code here
let book = "My Company"
let db = DB()
db.analysts book |> Array.iter (printfn "%A")
because, of course, in the example above ConfigurationManager would not be usable from the scripting engine, so I need to implement a change at preprocessing time.
More generally, where can I find the documentation or a at least a list of all the available, standard symbols, that are already automatically defined, including COMPILED and so on...?
From Compiler directives F#:
Symbols that you use in the if directive must be defined by the command line or in the project settings; there is no define preprocessor directive...
When VERSION1 is defined by using the -define compiler option, the code between the #if directive and the #else directive is activated. Otherwise, the code between #else and #endif is activated.
So you can defined your own preprocessor directives when compile code. If you work with .NET Core, inside fsproj or csproj you can define these symbols as:
<PropertyGroup Condition="'$(TargetFramework)' != 'net40'">
<DefineConstants>NET45</DefineConstants>
</PropertyGroup>
dotnet sends them to fsc. If you curious what symbols are defined by default, you can investigate fsc source code
I found COMPILED and INTERACTIVE there.

Sharing const variables across FAKE fsx scripts

Is there any way to share a variable by including a fsx script within another fsx script.
e.g script buildConsts.fsx contains
let buildDir = "./build/"
I want to reference this in other build scripts e.g.
#load #".\buildConsts.fsx"
let testDlls = !! (buildDir + "*Test*.dll")
When I attempt to run the script the 'buildDir' variable the script fails to compile.
This is a fairly common approach that is used with tools such as MSBuild and PSAKE to modularise scripts. Is this the correct approach with FAKE ?
What you're doing should work - what exactly is the error message that you're getting?
I suspect that the problem is that F# automatically puts the contents of a file in a module and you need to open the module before you can access the constants. The module is named based on the file name, so in your case buildConsts.fsx will generate a module named BuildConsts. You should be able to use it as follows:
#load #".\buildConsts.fsx"
open BuildConsts
let testDlls = !! (buildDir + "*Test*.dll")
You can also add an explicit module declaration to buildconsts.fsx, which is probably a better idea as it is less fragile (won't change when you rename the file):
moule BuildConstants
let buildDir = "./build/"

Fsx execution path

I have a c# .net library I am looking to use within FSI/FSX. As part of the initialization of the .net lib, by default it expects and references a custom config file (MyAppConfig.xml) which loads various things before it can be used. When using it in c# it gets copied to the bin folder and the app by default expects it to be there and references it there unless there is a specific entry in the app.config to tell it otherwise. (I should add that it does it all by convention rather than injecting a path + filename, as per NLog, say)
I have an f# source file in a console app which will execute this initialization find, but I can't quite work out how to achieve this with FSI/FSX.
So my program.fs looks simply like
open System
open myApp
module Program =
[<EntryPoint>]
let Main(args) =
myApp.Initialization.Load() // references MyAppConfig.xml
Console.WriteLine("do my stuff!")
Console.ReadLine() |> ignore
0
If I try and do the same in FSI or using FSX, I have
#r #"E:\...path to MyApp...\MyApp.dll"
#I #"E:\...path to MyAppConfig.xml ..."
Environment.CurrentDirectory <- #"E:\...path to MyAppConfig.xml ..."
myApp.Initialization.Load() |> ignore // fails ... can't find MyAppConfig.xml
//do my stuff
I suspect that I've not got the paths quite right.
I'd be grateful of a steer
EDIT:
So I've managed to attach a debugger to the c# lib and see where it is looking for the config file - turns out it is "c:\Program Files\Microsoft F#\v4.0\" ( System.AppDomain.CurrentDomain.BaseDirectory) which again shows I've not quite understood how to tell FSI/FSX to use a particular path. If I copy the config file (MyAppConfig.xml) to that location it works fine.
Many thx
S
I'm not sure of the implications, but one possiblity might be temporarily changing the app base:
let origAppBase = AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.SetData("APPBASE", "path_to_MyAppConfig.xml")
myApp.Initialization.Load() |> ignore
AppDomain.CurrentDomain.SetData("APPBASE", origAppBase) //restore original app base

How can I get F# Interactive window to use the same path as the project?

In my project I am opening a file with some relative path to the executable. I was trying to test my code in the F# Interractive window, but it seems to run from a completely different path. How can I change the path/ make it run from the same path as the project?
I think __SOURCE_DIRECTORY__ identifier could help here.
You should use compiler directives to separate between using F# Interactive and compiling F# project.
#if INTERACTIVE
let path = __SOURCE_DIRECTORY__ + some_relative_path
#else
let path = another_relative_path
#endif
You could set the current working directory when running in FSI:
#if INTERACTIVE
System.IO.Directory.SetCurrentDirectory("<project_path>")
#endif

Resources