F# Kung Fu #4: Avoid using relative paths in #r directives

Today, Vladimir Makarov faced with quite interesting ‘bug'(very unexpected behavior) of FSI. The initial goal was quite simple – count number of NuGet packages, which have “ASP.NET” in title. As a result, there was created a script that perfectly works in compiled form and crashes in FSI, here it is:

#r "../packages/nuget.core.2.8.2/lib/net40-Client/Nuget.Core.dll"
#r "System.Xml.Linq.dll"

let repository =
    NuGet.PackageRepositoryFactory.Default.CreateRepository
        "https://nuget.org/api/v2"

let aspnet = query {
    for p in repository.GetPackages() do
    where (p.Title.Contains "ASP.NET")
    count
}

When we run this in FSI we got an exception

System.ArgumentException: Incorrect instance type
Parameter name: obj
at Microsoft.FSharp.Quotations.PatternsModule.mkInstanceMethodCall(FSharpExpr obj, MethodInfo minfo, FSharpList`1 args)
at Microsoft.FSharp.Quotations.ExprShapeModule.RebuildShapeCombination(Object shape, FSharpList`1 arguments)
at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x)
at Microsoft.FSharp.Linq.QueryModule.walk@933-1[a](FSharpFunc`2 f, FSharpExpr p)
at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedInner(CanEliminate canElim, FSharpExpr queryProducingSequence)
at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedOuter(CanEliminate canElim, FSharpExpr tm)
at Microsoft.FSharp.Linq.QueryModule.clo@1741-1.Microsoft-FSharp-Linq-ForwardDeclarations-IQueryMethods-Execute[a,b](FSharpExpr`1 )
at <StartupCode$FSI_0002>.$FSI_0002.main@() in D:\Downloads\test.fsx:line 4

When we looked more carefully to FSI output, we saw that:

nuget.core

WAT? FSI lies to us?! First message says that correct version of DLL was referenced, but then FSI loads completely wrong old version installed with ASP.NET. Why? Let’s check what #r actually does…

#r means to reference by dll-path; focusing on name. This means that FSI will use the file name first, looking in the system-wide search path and only then try to use the string after #r as a directory-relative hint

So that means, #r is not reliable way to reference assemblies. You can get into the situation when your script depends on the environment: assemblies in GAC, installed software (like version of ASP.NET) and so on. To avoid this it is better to explicitly specify an assembly search path (#I) and then reference the assembly:

#I "../packages/nuget.core.2.8.2/lib/net40-Client"
#r "Nuget.Core.dll"
#r "System.Xml.Linq.dll"

let repository =
    NuGet.PackageRepositoryFactory.Default.CreateRepository
        "https://nuget.org/api/v2"

let aspnet = query {
    for p in repository.GetPackages() do
    where (p.Title.Contains "ASP.NET")
    count
}

Thanks to Vladimir Makarov for interesting challenge and be careful in your scripts.

6 thoughts on “F# Kung Fu #4: Avoid using relative paths in #r directives

  1. This is also important for cross-platform scripts. The pattern you give of using #I followed by #r is required on mono because FSI uses a different assembly resolution method there and will not find the assembly otherwise.

  2. Referencing nuget packages with full paths is the thing I hate the most in FSI. I should be smarter and dead simple, ie #n “package name”. It should go and try to resolve package location.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s