I have a F# custom type provider (in this case CheckedRegexProvider); the relevant source is
[<TypeProvider>]
type public CheckedRegexProvider() as this =
inherit TypeProviderForNamespaces()
// Get the assembly and namespace used to house the provided types
let thisAssembly = Assembly.GetExecutingAssembly()
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let baseTy = typeof<obj>
let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]
let regexTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "RegexTyped", Some baseTy)
do regexTy.DefineStaticParameters(
parameters=staticParams,
instantiationFunction=(fun typeName parameterValues ->
match parameterValues with ...
Then I have a simple test project where I put some trivial code in a .fs
open Samples.FSharp.RegexTypeProvider
type T = RegexTyped< @"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)">
What puzzles me is that the instantiationFunction gets called many more times than I expected. I thought that function would be called only when I changed the static parameters (in this case "(?^\d{3})-(?\d{3}-\d{4}$)"), instead every time I do something in the test source (e.g. push the space bar), that lambda gets called twice consecutively. If I really change the parameter, it gets called twice or three times.
This of course has quite an impact on the IDE (in my case Visual Studio), especially because that function is meant to provide a type that likely requires to scan some data source for schema information.
I then tried to isolate the type provider invocation in a separate source module file (let's call it M1.fs), and open that module in the actual test code (M2.fs). With this, the lambda gets still called at every touch of M1.fs, but it's not called at all when I work in M2.fs, as expected.
What I am asking is: are those continuous re-calls to the instantiation function correct? Is that by design?
And if yes, why is that so?
Type providers are called at compile time, and the compiler then uses the types that they provide in compiling the rest of the code.
Nearly every IDE that handles F# code intelligently does so by calling the F# compiler service behind-the-scenes to build an AST (usually one source file at a time), and then does operations on that AST for things like providing Intellisense.
The F# compiler service does not cache the results of previous compilations, because any change you make may have effects elsewhere in the source file, both before and after it. The "after" part of that statement is for obvious reasons, but it may not be immediately obvious why a change in line 25 could affect line 10. The reason for that is type inference: if line 10 is let a = Array.zeroCreate 1024
, and line 25 is a.[0] <- 42
, then the type of a
will be inferred to be int[]
. If you change line 25 to a.[0] <- "forty-two"
, then the type of a
will be inferred to be string[]
, and the AST built for line 10 will be different. Therefore, the F# compiler service re-compiles the entire source file every time it's called.
Therefore, every time you edit your source file, the F# compiler service re-compiles the file. If that file is the one that includes your type provider definition, the compiler has to instantiate the type provider in order to compile the file, and so your instantiationFunction
has to be called every time the file is compiled.
So yes, the behavior you're seeing is by design.