I have a F# dotnet project with some dependencies in my .fsproj
(eg <PackageReference Include="FSharp.Data" Version="3.0.0-beta4" />
).
Now I want to test some functions in a module I wrote, so I start fsharpi
and try to load my module with #load "Program.fs";;
.
However, then I get error FS0039: The type 'CsvProvider' is not defined
.
How can I load my module with the correct dependencies?
I have seen workarounds which include manually loading all required dll
s from some obscure system dependent path (eg #load a package in F# interactive (FSharpChart.fsx) ), but I guess there must be a better way to do this.
In order to get access to the code in imported libraries, you need to tell the FSI to load them.
At the lowest level, this can be done with an #r
directive:
#r "./packages/FSharp.Data/lib/whatever/FSharp.Data.dll"
This is often sufficient for one-off purposes.
However, if there are a lot of references, your IDE can usually automate this for you. For example, in Visual Studio, you can right-click the project and select either "Send References to F# Interactive" or "Generate Script File with References" (not sure about the exact wording). The same options are available in VSCode if you have the Ionide extension installed.
Also, there is an active proposal+prototype for supporting packages in FSI directly - see https://github.com/fsharp/fslang-suggestions/issues/542 . However, that is not yet merged and seems to have stalled a bit.
There are a few option for doing this, none of them perfect.
If you are using Ionide, then you can right click on a project in F# view and choose "Generate References for FSI". This will create an F# script that loads all of the dependencies and project files for you. Write your test code below this.
The downsides of this approach:
If you are using Paket, then you can generate "load scripts" for your project:
dotnet paket generate-load-scripts
This will generate F# scripts in ./paket/load
. There will be a load script for each dependency (and its transitive dependencies) and for each Paket "group".
This can automatically sync with your paket.dependencies
if you add this line:
generate_load_scripts: true
See: https://fsprojects.github.io/Paket/paket-generate-load-scripts.html
The downsides of this approach:
#load
you project files manually New with .NET 5, you can write Nuget strings directly in your fsx
files. This is a great feature for quick scripts!
#r "nuget: FParsec"
// Package with a specific version
// #r "nuget: FParsec,1.1.1"
open FParsec
#load "./Types.fs"
// etc...
The downsides of this approach:
#load
you project files manuallyIn an ideal world, we would be able to do something like this:
#r "fsproj: ./my-proj.fsproj"
open FParsec
// etc...
There is an issue for this here: https://github.com/dotnet/fsharp/issues/8764
As requested in the comments on Fyodor's answer, here's a script that I've used in the past to generate the #r
and #load
directives required to load an .fsproj
in F# Interactive:
module References
#r "System.Xml"
#r "System.Xml.Linq"
open System
open System.IO
open System.Linq
open System.Xml.Linq
let inline isNotNull o = o |> isNull |> not
let private getProject path =
Directory.EnumerateFiles(path, "*.*proj") |> Seq.head |> XDocument.Load
let generateDlls path =
let projectFile = getProject path
let references =
projectFile.Descendants <| XName.Get("HintPath", "http://schemas.microsoft.com/developer/msbuild/2003")
|> Seq.filter (fun reference -> reference.Value.ToLower().EndsWith(".dll"))
|> Seq.filter (fun reference -> reference.Value.StartsWith("$") |> not)
|> Seq.map (fun reference -> reference.Value)
let projects =
projectFile.Descendants <| XName.Get("ProjectReference", "http://schemas.microsoft.com/developer/msbuild/2003")
|> Seq.map (fun reference -> reference.Elements(XName.Get("Name", "http://schemas.microsoft.com/developer/msbuild/2003")).SingleOrDefault())
|> Seq.filter (fun nameElement -> nameElement |> isNotNull)
|> Seq.map (fun nameElement -> nameElement.Value)
|> Seq.map (fun reference -> sprintf "../%s/bin/debug/%s.dll" reference reference)
references
|> Seq.append projects
|> Seq.iter (fun reference -> printfn "#r @\"%s\"" reference)
let generateFs path =
let projectFile = getProject path
projectFile.Descendants <| XName.Get("Compile", "http://schemas.microsoft.com/developer/msbuild/2003")
|> Seq.map (fun reference -> reference.Attribute("Include" |> XName.op_Implicit))
|> Seq.filter (fun reference -> reference |> isNotNull && reference.Value.ToLower().EndsWith("fs"))
|> Seq.filter (fun reference -> reference.Value.Equals("AssemblyInfo.fs", StringComparison.CurrentCultureIgnoreCase) |> not)
|> Seq.iter (fun reference -> printfn "#load @\"%s\"" reference.Value)
// Example Usage:
// #load @"GenerateReferences.fsx"
// References.generateDlls __SOURCE_DIRECTORY__
// References.generateFs __SOURCE_DIRECTORY__
I'm not sure this is completely perfect, for instance, I don't think it gets dependency-ordering right. However, it should be a reasonable starting point if you want to enhance it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.