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:
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.
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.
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.
Or download if not found
Does #I replace or append to the search path?
Yes, it is similar, but it work only for surrent script / FSI session https://msdn.microsoft.com/en-us/library/dd233175.aspx